//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace Microsoft.Azure.Cosmos
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using global::Azure;
using global::Azure.Core;
using Microsoft.Azure.Cosmos.Authorization;
using Microsoft.Azure.Cosmos.Handlers;
using Microsoft.Azure.Cosmos.Query.Core.Monads;
using Microsoft.Azure.Cosmos.Query.Core.QueryPlan;
using Microsoft.Azure.Cosmos.Telemetry;
using Microsoft.Azure.Cosmos.Telemetry.OpenTelemetry;
using Microsoft.Azure.Cosmos.Tracing;
using Microsoft.Azure.Cosmos.Tracing.TraceData;
using Microsoft.Azure.Documents;
using ResourceType = Documents.ResourceType;
///
/// Provides a client-side logical representation of the Azure Cosmos DB account.
/// This client can be used to configure and execute requests in the Azure Cosmos DB database service.
///
/// CosmosClient is thread-safe. Its recommended to maintain a single instance of CosmosClient per lifetime
/// of the application which enables efficient connection management and performance. Please refer to the
/// performance guide.
///
///
/// This example create a , , and a .
/// The CosmosClient is created with the connection string and configured to use "East US 2" region.
///
///
///
///
///
/// This example creates a , , and a .
/// The CosmosClient is created with the AccountEndpoint, AccountKey or ResourceToken and configured to use "East US 2" region.
///
///
///
///
///
/// This example creates a , , and a .
/// The CosmosClient is created through builder pattern using .
///
///
///
///
///
/// The returned not-initialized reference doesn't guarantee credentials or connectivity validations because creation doesn't make any network calls
///
///
///
/// Performance Tips
/// Diagnose and troubleshoot issues
/// Global data distribution
/// Partitioning and horizontal scaling
/// Request Units
public class CosmosClient : IDisposable
{
internal readonly string Id = Guid.NewGuid().ToString();
private readonly object disposedLock = new object();
private readonly string DatabaseRootUri = Paths.Databases_Root;
private ConsistencyLevel? accountConsistencyLevel;
private bool isDisposed = false;
internal static int numberOfClientsCreated;
internal static int NumberOfActiveClients;
internal DateTime? DisposedDateTimeUtc { get; private set; } = null;
static CosmosClient()
{
HttpConstants.Versions.CurrentVersion = HttpConstants.Versions.v2020_07_15;
HttpConstants.Versions.CurrentVersionUTF8 = Encoding.UTF8.GetBytes(HttpConstants.Versions.CurrentVersion);
ServiceInteropWrapper.AssembliesExist = new Lazy(() =>
{
// Attemp to create an instance of the ServiceInterop assembly
TryCatch tryCreateServiceProvider = QueryPartitionProvider.TryCreateServiceProvider("{}");
if (tryCreateServiceProvider.Failed)
{
// Failed, either the DLL is not present or one of its dependencies
return false;
}
// Assembly and dependencies are available
// Release pointer
Marshal.Release(tryCreateServiceProvider.Result);
return true;
});
Microsoft.Azure.Cosmos.Core.Trace.DefaultTrace.InitEventListener();
// If a debugger is not attached remove the DefaultTraceListener.
// DefaultTraceListener can cause lock contention leading to availability issues
if (!Debugger.IsAttached)
{
CosmosClient.RemoveDefaultTraceListener();
}
}
///
/// Create a new CosmosClient used for mock testing
///
protected CosmosClient()
{
}
///
/// Creates a new CosmosClient with the connection string.
///
/// CosmosClient is thread-safe. Its recommended to maintain a single instance of CosmosClient per lifetime
/// of the application which enables efficient connection management and performance. Please refer to the
/// performance guide.
///
/// The connection string to the cosmos account. ex: AccountEndpoint=https://XXXXX.documents.azure.com:443/;AccountKey=SuperSecretKey;
/// (Optional) client options
///
/// The CosmosClient is created with the connection string and configured to use "East US 2" region.
///
///
///
///
///
/// Emulator: To ignore SSL Certificate please suffix connectionstring with "DisableServerCertificateValidation=True;".
/// When CosmosClientOptions.HttpClientFactory is used, SSL certificate needs to be handled appropriately.
/// NOTE: DO NOT use this flag in production (only for emulator)
///
///
///
/// Performance Tips
/// Diagnose and troubleshoot issues
public CosmosClient(
string connectionString,
CosmosClientOptions clientOptions = null)
: this(
CosmosClientOptions.GetAccountEndpoint(connectionString),
CosmosClientOptions.GetAccountKey(connectionString),
CosmosClientOptions.GetCosmosClientOptionsWithCertificateFlag(connectionString, clientOptions))
{
}
///
/// Creates a new CosmosClient with the account endpoint URI string and account key.
///
/// CosmosClient is thread-safe. Its recommended to maintain a single instance of CosmosClient per lifetime
/// of the application which enables efficient connection management and performance. Please refer to the
/// performance guide.
///
/// The cosmos service endpoint to use
/// The cosmos account key or resource token to use to create the client.
/// (Optional) client options
///
/// The CosmosClient is created with the AccountEndpoint, AccountKey or ResourceToken and configured to use "East US 2" region.
///
///
///
///
///
/// The returned reference doesn't guarantee credentials or connectivity validations because creation doesn't make any network calls.
///
///
///
/// Performance Tips
/// Diagnose and troubleshoot issues
public CosmosClient(
string accountEndpoint,
string authKeyOrResourceToken,
CosmosClientOptions clientOptions = null)
: this(accountEndpoint,
AuthorizationTokenProvider.CreateWithResourceTokenOrAuthKey(authKeyOrResourceToken),
clientOptions)
{
}
///
/// Creates a new CosmosClient with the account endpoint URI string and AzureKeyCredential.
/// AzureKeyCredential enables changing/updating master-key/ResourceToken while CosmosClient is still in use.
///
/// CosmosClient is thread-safe. Its recommended to maintain a single instance of CosmosClient per lifetime
/// of the application which enables efficient connection management and performance. Please refer to the
/// performance guide.
///
/// The cosmos service endpoint to use
/// AzureKeyCredential with master-key or resource token..
/// (Optional) client options
///
/// The CosmosClient is created with the AccountEndpoint, AccountKey or ResourceToken and configured to use "East US 2" region.
///
///
///
///
///
///
/// Performance Tips
/// Diagnose and troubleshoot issues
///
/// AzureKeyCredential enables changing/updating master-key/ResourceToken whle CosmosClient is still in use.
/// The returned reference doesn't guarantee credentials or connectivity validations because creation doesn't make any network calls.
///
public CosmosClient(
string accountEndpoint,
AzureKeyCredential authKeyOrResourceTokenCredential,
CosmosClientOptions clientOptions = null)
: this(accountEndpoint,
new AzureKeyCredentialAuthorizationTokenProvider(authKeyOrResourceTokenCredential),
clientOptions)
{
}
///
/// Creates a new CosmosClient with the account endpoint URI string and TokenCredential.
///
/// CosmosClient is thread-safe. Its recommended to maintain a single instance of CosmosClient per lifetime
/// of the application which enables efficient connection management and performance. Please refer to the
/// performance guide.
///
///
/// The returned reference doesn't guarantee credentials or connectivity validations because creation doesn't make any network calls.
///
/// The cosmos service endpoint to use.
/// The token to provide AAD token for authorization.
/// (Optional) client options
public CosmosClient(
string accountEndpoint,
TokenCredential tokenCredential,
CosmosClientOptions clientOptions = null)
: this(accountEndpoint,
new AuthorizationTokenProviderTokenCredential(
tokenCredential,
new Uri(accountEndpoint),
clientOptions?.TokenCredentialBackgroundRefreshInterval,
AuthorizationTokenProviderTokenCredential.GenerateAadAuthorizationSignature),
clientOptions)
{
}
///
/// Used by Compute
/// Creates a new CosmosClient with the AuthorizationTokenProvider
///
internal CosmosClient(
string accountEndpoint,
AuthorizationTokenProvider authorizationTokenProvider,
CosmosClientOptions clientOptions)
{
if (string.IsNullOrEmpty(accountEndpoint))
{
throw new ArgumentNullException(nameof(accountEndpoint));
}
this.Endpoint = new Uri(accountEndpoint);
this.AuthorizationTokenProvider = authorizationTokenProvider ?? throw new ArgumentNullException(nameof(authorizationTokenProvider));
clientOptions ??= new CosmosClientOptions();
this.ClientId = this.IncrementNumberOfClientsCreated();
this.ClientContext = ClientContextCore.Create(
this,
clientOptions);
this.ClientConfigurationTraceDatum = new ClientConfigurationTraceDatum(this.ClientContext, DateTime.UtcNow);
}
///
/// Creates a new CosmosClient with the account endpoint URI string and TokenCredential.
/// In addition to that it initializes the client with containers provided i.e The SDK warms up the caches and
/// connections before the first call to the service is made. Use this to obtain lower latency while startup of your application.
/// CosmosClient is thread-safe. Its recommended to maintain a single instance of CosmosClient per lifetime
/// of the application which enables efficient connection management and performance. Please refer to the
/// performance guide.
///
/// The cosmos service endpoint to use
/// The cosmos account key or resource token to use to create the client.
/// Containers to be initialized identified by it's database name and container name.
/// (Optional) client options
/// (Optional) Cancellation Token
///
/// A CosmosClient object.
///
///
/// The CosmosClient is created with the AccountEndpoint, AccountKey or ResourceToken and 2 containers in the account are initialized
///
/// containersToInitialize = new List<(string, string)>
/// { ("DatabaseName1", "ContainerName1"), ("DatabaseName2", "ContainerName2") };
///
/// CosmosClient cosmosClient = await CosmosClient.CreateAndInitializeAsync("account-endpoint-from-portal",
/// "account-key-from-portal",
/// containersToInitialize)
///
/// // Dispose cosmosClient at application exit
/// ]]>
///
///
///
/// The returned reference doesn't guarantee credentials or connectivity validations because initialization doesn't make any network calls.
///
public static async Task CreateAndInitializeAsync(string accountEndpoint,
string authKeyOrResourceToken,
IReadOnlyList<(string databaseId, string containerId)> containers,
CosmosClientOptions cosmosClientOptions = null,
CancellationToken cancellationToken = default)
{
if (containers == null)
{
throw new ArgumentNullException(nameof(containers));
}
CosmosClient cosmosClient = new CosmosClient(accountEndpoint,
authKeyOrResourceToken,
cosmosClientOptions);
await cosmosClient.InitializeContainersAsync(containers, cancellationToken);
return cosmosClient;
}
///
/// Creates a new CosmosClient with the account endpoint URI string and AzureKeyCredential.
/// AzureKeyCredential enables changing/updating master-key/ResourceToken while CosmosClient is still in use.
///
/// In addition to that it initializes the client with containers provided i.e The SDK warms up the caches and
/// connections before the first call to the service is made. Use this to obtain lower latency while startup of your application.
/// CosmosClient is thread-safe. Its recommended to maintain a single instance of CosmosClient per lifetime
/// of the application which enables efficient connection management and performance. Please refer to the
/// performance guide.
///
/// The cosmos service endpoint to use
/// AzureKeyCredential with master-key or resource token.
/// Containers to be initialized identified by it's database name and container name.
/// (Optional) client options
/// (Optional) Cancellation Token
///
/// A CosmosClient object.
///
///
/// The CosmosClient is created with the AccountEndpoint, AccountKey or ResourceToken and 2 containers in the account are initialized
///
/// containersToInitialize = new List<(string, string)>
/// { ("DatabaseName1", "ContainerName1"), ("DatabaseName2", "ContainerName2") };
///
/// AzureKeyCredential keyCredential = new AzureKeyCredential("account-master-key/ResourceToken");
/// CosmosClient cosmosClient = await CosmosClient.CreateAndInitializeAsync("account-endpoint-from-portal",
/// keyCredential,
/// containersToInitialize)
///
/// ....
///
/// // To udpate key/credentials
/// keyCredential.Update("updated master-key/ResourceToken");
///
/// // Dispose cosmosClient at application exit
/// ]]>
///
///
/// AzureKeyCredential enables changing/updating master-key/ResourceToken whle CosmosClient is still in use.
public static async Task CreateAndInitializeAsync(string accountEndpoint,
AzureKeyCredential authKeyOrResourceTokenCredential,
IReadOnlyList<(string databaseId, string containerId)> containers,
CosmosClientOptions cosmosClientOptions = null,
CancellationToken cancellationToken = default)
{
if (containers == null)
{
throw new ArgumentNullException(nameof(containers));
}
CosmosClient cosmosClient = new CosmosClient(accountEndpoint,
authKeyOrResourceTokenCredential,
cosmosClientOptions);
await cosmosClient.InitializeContainersAsync(containers, cancellationToken);
return cosmosClient;
}
///
/// Creates a new CosmosClient with the account endpoint URI string and TokenCredential.
/// In addition to that it initializes the client with containers provided i.e The SDK warms up the caches and
/// connections before the first call to the service is made. Use this to obtain lower latency while startup of your application.
/// CosmosClient is thread-safe. Its recommended to maintain a single instance of CosmosClient per lifetime
/// of the application which enables efficient connection management and performance. Please refer to the
/// performance guide.
///
/// The connection string to the cosmos account. ex: AccountEndpoint=https://XXXXX.documents.azure.com:443/;AccountKey=SuperSecretKey;
/// Containers to be initialized identified by it's database name and container name.
/// (Optional) client options
/// (Optional) Cancellation Token
///
/// A CosmosClient object.
///
///
/// The CosmosClient is created with the ConnectionString and 2 containers in the account are initialized
///
/// containersToInitialize = new List<(string, string)>
/// { ("DatabaseName1", "ContainerName1"), ("DatabaseName2", "ContainerName2") };
///
/// CosmosClient cosmosClient = await CosmosClient.CreateAndInitializeAsync("connection-string-from-portal",
/// containersToInitialize)
///
/// // Dispose cosmosClient at application exit
/// ]]>
///
///
///
/// Emulator: To ignore SSL Certificate please suffix connectionstring with "DisableServerCertificateValidation=True;".
/// When CosmosClientOptions.HttpClientFactory is used, SSL certificate needs to be handled appropriately.
/// NOTE: DO NOT use this flag in production (only for emulator)
///
public static async Task CreateAndInitializeAsync(string connectionString,
IReadOnlyList<(string databaseId, string containerId)> containers,
CosmosClientOptions cosmosClientOptions = null,
CancellationToken cancellationToken = default)
{
if (containers == null)
{
throw new ArgumentNullException(nameof(containers));
}
cosmosClientOptions = CosmosClientOptions.GetCosmosClientOptionsWithCertificateFlag(connectionString, cosmosClientOptions);
CosmosClient cosmosClient = new CosmosClient(connectionString,
cosmosClientOptions);
await cosmosClient.InitializeContainersAsync(containers, cancellationToken);
return cosmosClient;
}
///
/// Creates a new CosmosClient with the account endpoint URI string and TokenCredential.
/// In addition to that it initializes the client with containers provided i.e The SDK warms up the caches and
/// connections before the first call to the service is made. Use this to obtain lower latency while startup of your application.
/// CosmosClient is thread-safe. Its recommended to maintain a single instance of CosmosClient per lifetime
/// of the application which enables efficient connection management and performance. Please refer to the
/// performance guide.
///
/// The cosmos service endpoint to use.
/// The token to provide AAD token for authorization.
/// Containers to be initialized identified by it's database name and container name.
/// (Optional) client options
/// (Optional) Cancellation Token
///
/// A CosmosClient object.
///
public static async Task CreateAndInitializeAsync(string accountEndpoint,
TokenCredential tokenCredential,
IReadOnlyList<(string databaseId, string containerId)> containers,
CosmosClientOptions cosmosClientOptions = null,
CancellationToken cancellationToken = default)
{
if (containers == null)
{
throw new ArgumentNullException(nameof(containers));
}
CosmosClient cosmosClient = new CosmosClient(accountEndpoint,
tokenCredential,
cosmosClientOptions);
await cosmosClient.InitializeContainersAsync(containers, cancellationToken);
return cosmosClient;
}
///
/// Used for unit testing only.
///
/// This constructor should be removed at some point. The mocking should happen in a derived class.
internal CosmosClient(
string accountEndpoint,
string authKeyOrResourceToken,
CosmosClientOptions cosmosClientOptions,
DocumentClient documentClient)
{
if (string.IsNullOrEmpty(accountEndpoint))
{
throw new ArgumentNullException(nameof(accountEndpoint));
}
if (string.IsNullOrEmpty(authKeyOrResourceToken))
{
throw new ArgumentNullException(nameof(authKeyOrResourceToken));
}
if (cosmosClientOptions == null)
{
throw new ArgumentNullException(nameof(cosmosClientOptions));
}
if (documentClient == null)
{
throw new ArgumentNullException(nameof(documentClient));
}
this.Endpoint = new Uri(accountEndpoint);
this.AccountKey = authKeyOrResourceToken;
this.AuthorizationTokenProvider = AuthorizationTokenProvider.CreateWithResourceTokenOrAuthKey(authKeyOrResourceToken);
this.ClientContext = ClientContextCore.Create(
this,
documentClient,
cosmosClientOptions);
this.ClientConfigurationTraceDatum = new ClientConfigurationTraceDatum(this.ClientContext, DateTime.UtcNow);
}
///
/// The used initialize CosmosClient.
///
/// This property is read-only. Modifying any options after the client has been created has no effect on the existing client instance.
public virtual CosmosClientOptions ClientOptions => this.ClientContext.ClientOptions;
#if PREVIEW
///
/// Gets the client-wide , or null if none was set.
/// Set via or
/// .
///
public virtual ICosmosEmbeddingGenerator EmbeddingGenerator => this.ClientContext.ClientOptions.EmbeddingGenerator;
#endif
///
/// The response factory used to create CosmosClient response types.
///
///
/// This can be used for generating responses for tests, and allows users to create
/// a custom container that modifies the response. For example the client encryption
/// uses this to decrypt responses before returning to the caller.
///
public virtual CosmosResponseFactory ResponseFactory => this.ClientContext.ResponseFactory;
///
/// Gets the endpoint Uri for the Azure Cosmos DB service.
///
///
/// The Uri for the account endpoint.
///
///
public virtual Uri Endpoint { get; }
///
/// Gets the AuthKey or resource token used by the client from the Azure Cosmos DB service.
///
///
/// The AuthKey used by the client.
///
internal string AccountKey { get; }
///
/// Gets the AuthorizationTokenProvider used to generate the authorization token
///
internal AuthorizationTokenProvider AuthorizationTokenProvider { get; }
internal DocumentClient DocumentClient => this.ClientContext.DocumentClient;
internal RequestInvokerHandler RequestHandler => this.ClientContext.RequestHandler;
internal CosmosClientContext ClientContext { get; }
internal ClientConfigurationTraceDatum ClientConfigurationTraceDatum { get; }
internal int ClientId { get; }
///
/// Reads the for the Azure Cosmos DB account.
///
///
/// A wrapped in a object.
///
public virtual Task ReadAccountAsync()
{
return this.ClientContext.OperationHelperAsync(
operationName: nameof(ReadAccountAsync),
containerName: null,
databaseName: null,
operationType: OperationType.Read,
requestOptions: null,
task: (trace) => ((IDocumentClientInternal)this.DocumentClient).GetDatabaseAccountInternalAsync(this.Endpoint));
}
///
/// Returns a proxy reference to a database.
///
/// The Cosmos database id
///
/// proxy reference doesn't guarantee existence.
/// Please ensure database exists through
/// or , before
/// operating on it.
///
///
///
///
///
///
/// Cosmos database proxy
public virtual Database GetDatabase(string id)
{
return new DatabaseInlineCore(this.ClientContext, id);
}
///
/// Returns a proxy reference to a container.
///
///
/// proxy reference doesn't guarantee existence.
/// Please ensure container exists through
/// or , before
/// operating on it.
///
/// Cosmos database name
/// Cosmos container name
/// Cosmos container proxy
public virtual Container GetContainer(string databaseId, string containerId)
{
if (string.IsNullOrEmpty(databaseId))
{
throw new ArgumentNullException(nameof(databaseId));
}
if (string.IsNullOrEmpty(containerId))
{
throw new ArgumentNullException(nameof(containerId));
}
return this.GetDatabase(databaseId).GetContainer(containerId);
}
///
/// Sends a request for creating a database.
///
/// A database manages users, permissions and a set of containers.
/// Each Azure Cosmos DB Database Account is able to support multiple independent named databases,
/// with the database being the logical container for data.
///
/// Each Database consists of one or more containers, each of which in turn contain one or more
/// documents. Since databases are an administrative resource, the Service Master Key will be
/// required in order to access and successfully complete any action using the User APIs.
///
/// The database id.
/// (Optional) The throughput provisioned for a database in measurement of Request Units per second in the Azure Cosmos DB service.
/// (Optional) A set of options that can be set.
/// (Optional) representing request cancellation.
/// A containing a which wraps a containing the resource record.
/// https://aka.ms/cosmosdb-dot-net-exceptions
/// Request Units
public virtual Task CreateDatabaseAsync(
string id,
int? throughput = null,
RequestOptions requestOptions = null,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(id))
{
throw new ArgumentNullException(nameof(id));
}
return this.ClientContext.OperationHelperAsync(
operationName: nameof(CreateDatabaseAsync),
containerName: null,
databaseName: id,
operationType: OperationType.Create,
requestOptions: requestOptions,
task: (trace) =>
{
DatabaseProperties databaseProperties = this.PrepareDatabaseProperties(id);
ThroughputProperties throughputProperties = ThroughputProperties.CreateManualThroughput(throughput);
return this.CreateDatabaseInternalAsync(
databaseProperties: databaseProperties,
throughputProperties: throughputProperties,
requestOptions: requestOptions,
trace: trace,
cancellationToken: cancellationToken);
},
openTelemetry: new (OpenTelemetryConstants.Operations.CreateDatabase, (response) => new OpenTelemetryResponse(responseMessage: response)));
}
///
/// Sends a request for creating a database.
///
/// A database manages users, permissions and a set of containers.
/// Each Azure Cosmos DB Database Account is able to support multiple independent named databases,
/// with the database being the logical container for data.
///
/// Each Database consists of one or more containers, each of which in turn contain one or more
/// documents. Since databases are an administrative resource, the Service Master Key will be
/// required in order to access and successfully complete any action using the User APIs.
///
/// The database id.
/// (Optional) The throughput provisioned for a database in measurement of Request Units per second in the Azure Cosmos DB service.
/// (Optional) A set of options that can be set.
/// (Optional) representing request cancellation.
/// A containing a which wraps a containing the resource record.
/// https://aka.ms/cosmosdb-dot-net-exceptions
/// Request Units
public virtual Task CreateDatabaseAsync(
string id,
ThroughputProperties throughputProperties,
RequestOptions requestOptions = null,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(id))
{
throw new ArgumentNullException(nameof(id));
}
return this.ClientContext.OperationHelperAsync(
operationName: nameof(CreateDatabaseAsync),
containerName: null,
databaseName: id,
operationType: OperationType.Create,
requestOptions: requestOptions,
task: (trace) =>
{
DatabaseProperties databaseProperties = this.PrepareDatabaseProperties(id);
return this.CreateDatabaseInternalAsync(
databaseProperties: databaseProperties,
throughputProperties: throughputProperties,
requestOptions: requestOptions,
trace: trace,
cancellationToken: cancellationToken);
},
openTelemetry: new (OpenTelemetryConstants.Operations.CreateDatabase, (response) => new OpenTelemetryResponse(responseMessage: response)));
}
///
/// Check if a database exists, and if it doesn't, create it.
/// Only the database id is used to verify if there is an existing database. Other database properties
/// such as throughput are not validated and can be different then the passed properties.
///
/// A database manages users, permissions and a set of containers.
/// Each Azure Cosmos DB Database Account is able to support multiple independent named databases,
/// with the database being the logical container for data.
///
/// Each Database consists of one or more containers, each of which in turn contain one or more
/// documents. Since databases are an administrative resource, the Service Master Key will be
/// required in order to access and successfully complete any action using the User APIs.
///
/// The database id.
/// The throughput provisioned for a database in measurement of Request Units per second in the Azure Cosmos DB service.
/// (Optional) A set of additional options that can be set.
/// (Optional) representing request cancellation.
/// A containing a which wraps a containing the resource record.
///
///
/// StatusCodeCommon success StatusCodes for the CreateDatabaseIfNotExistsAsync operation
///
/// -
/// 201Created - New database is created.
///
/// -
/// 200OK - This means the database already exists.
///
///
///
/// https://aka.ms/cosmosdb-dot-net-exceptions
/// Request Units
public virtual Task CreateDatabaseIfNotExistsAsync(
string id,
ThroughputProperties throughputProperties,
RequestOptions requestOptions = null,
CancellationToken cancellationToken = default)
{
return string.IsNullOrEmpty(id)
? throw new ArgumentNullException(nameof(id))
: this.ClientContext.OperationHelperAsync(
operationName: nameof(CreateDatabaseIfNotExistsAsync),
containerName: null,
databaseName: id,
operationType: OperationType.Create,
requestOptions: requestOptions,
task: async (trace) =>
{
double totalRequestCharge = 0;
// Doing a Read before Create will give us better latency for existing databases
DatabaseProperties databaseProperties = this.PrepareDatabaseProperties(id);
DatabaseCore database = (DatabaseCore)this.GetDatabase(id);
using (ResponseMessage readResponse = await database.ReadStreamAsync(
requestOptions: requestOptions,
trace: trace,
cancellationToken: cancellationToken))
{
totalRequestCharge = readResponse.Headers.RequestCharge;
if (readResponse.StatusCode != HttpStatusCode.NotFound)
{
return this.ClientContext.ResponseFactory.CreateDatabaseResponse(database, readResponse);
}
}
using (ResponseMessage createResponse = await this.CreateDatabaseStreamInternalAsync(
databaseProperties,
throughputProperties,
requestOptions,
trace,
cancellationToken))
{
totalRequestCharge += createResponse.Headers.RequestCharge;
createResponse.Headers.RequestCharge = totalRequestCharge;
if (createResponse.StatusCode != HttpStatusCode.Conflict)
{
return this.ClientContext.ResponseFactory.CreateDatabaseResponse(this.GetDatabase(databaseProperties.Id), createResponse);
}
}
// This second Read is to handle the race condition when 2 or more threads have Read the database and only one succeeds with Create
// so for the remaining ones we should do a Read instead of throwing Conflict exception
using (ResponseMessage readResponseAfterConflict = await database.ReadStreamAsync(
requestOptions: requestOptions,
trace: trace,
cancellationToken: cancellationToken))
{
totalRequestCharge += readResponseAfterConflict.Headers.RequestCharge;
readResponseAfterConflict.Headers.RequestCharge = totalRequestCharge;
return this.ClientContext.ResponseFactory.CreateDatabaseResponse(this.GetDatabase(databaseProperties.Id), readResponseAfterConflict);
}
},
openTelemetry: new (OpenTelemetryConstants.Operations.CreateDatabaseIfNotExists, (response) => new OpenTelemetryResponse(responseMessage: response)));
}
///
/// Check if a database exists, and if it doesn't, create it.
/// Only the database id is used to verify if there is an existing database. Other database properties
/// such as throughput are not validated and can be different then the passed properties.
///
/// A database manages users, permissions and a set of containers.
/// Each Azure Cosmos DB Database Account is able to support multiple independent named databases,
/// with the database being the logical container for data.
///
/// Each Database consists of one or more containers, each of which in turn contain one or more
/// documents. Since databases are an administrative resource, the Service Master Key will be
/// required in order to access and successfully complete any action using the User APIs.
///
/// The database id.
/// (Optional) The throughput provisioned for a database in measurement of Request Units per second in the Azure Cosmos DB service.
/// (Optional) A set of additional options that can be set.
/// (Optional) representing request cancellation.
/// A containing a which wraps a containing the resource record.
///
///
/// StatusCodeCommon success StatusCodes for the CreateDatabaseIfNotExistsAsync operation
///
/// -
/// 201Created - New database is created.
///
/// -
/// 200OK- This means the database already exists.
///
///
///
/// https://aka.ms/cosmosdb-dot-net-exceptions
/// Request Units
public virtual Task CreateDatabaseIfNotExistsAsync(
string id,
int? throughput = null,
RequestOptions requestOptions = null,
CancellationToken cancellationToken = default)
{
ThroughputProperties throughputProperties = ThroughputProperties.CreateManualThroughput(throughput);
return this.CreateDatabaseIfNotExistsAsync(
id,
throughputProperties,
requestOptions,
cancellationToken);
}
///
/// This method creates a query for databases under an Cosmos DB Account using a SQL statement with parameterized values. It returns a FeedIterator.
/// For more information on preparing SQL statements with parameterized values, please see .
///
/// The cosmos SQL query definition.
/// The continuation token in the Azure Cosmos DB service.
/// (Optional) The options for the item query request.
/// An iterator to go through the databases.
/// https://aka.ms/cosmosdb-dot-net-exceptions
///
/// Refer to https://docs.microsoft.com/azure/cosmos-db/sql-query-getting-started for syntax and examples.
///
/// is recommended for single database look-up.
///
///
///
/// This create the type feed iterator for database with queryText as input,
///
/// feedIterator = this.users.GetDatabaseQueryIterator(queryDefinition))
/// {
/// while (feedIterator.HasMoreResults)
/// {
/// FeedResponse response = await feedIterator.ReadNextAsync();
/// foreach (var database in response)
/// {
/// Console.WriteLine(database);
/// }
/// }
/// }
/// ]]>
///
///
public virtual FeedIterator GetDatabaseQueryIterator(
QueryDefinition queryDefinition,
string continuationToken = null,
QueryRequestOptions requestOptions = null)
{
return new FeedIteratorInlineCore(
this.GetDatabaseQueryIteratorHelper(
queryDefinition,
continuationToken,
requestOptions),
this.ClientContext);
}
///
/// This method creates a query for databases under an Cosmos DB Account using a SQL statement with parameterized values. It returns a FeedIterator.
/// For more information on preparing SQL statements with parameterized values, please see .
///
/// The cosmos SQL query definition.
/// The continuation token in the Azure Cosmos DB service.
/// (Optional) The options for the query request.
/// An iterator to go through the databases
/// https://aka.ms/cosmosdb-dot-net-exceptions
///
/// Refer to https://docs.microsoft.com/azure/cosmos-db/sql-query-getting-started for syntax and examples.
///
/// is recommended for single database look-up.
///
///
///
/// Example on how to fully drain the query results.
///
///
///
///
public virtual FeedIterator GetDatabaseQueryStreamIterator(
QueryDefinition queryDefinition,
string continuationToken = null,
QueryRequestOptions requestOptions = null)
{
return new FeedIteratorInlineCore(
this.GetDatabaseQueryStreamIteratorHelper(
queryDefinition,
continuationToken,
requestOptions),
this.ClientContext);
}
///
/// This method creates a query for databases under an Cosmos DB Account using a SQL statement. It returns a FeedIterator.
///
/// The cosmos SQL query text.
/// The continuation token in the Azure Cosmos DB service.
/// (Optional) The options for the item query request.
/// An iterator to go through the databases.
/// https://aka.ms/cosmosdb-dot-net-exceptions
///
/// Refer to https://docs.microsoft.com/azure/cosmos-db/sql-query-getting-started for syntax and examples.
///
/// is recommended for single database look-up.
///
///
///
/// This create the type feed iterator for database with queryText as input,
///
/// feedIterator = this.users.GetDatabaseQueryIterator(queryText)
/// {
/// while (feedIterator.HasMoreResults)
/// {
/// FeedResponse response = await feedIterator.ReadNextAsync();
/// foreach (var database in response)
/// {
/// Console.WriteLine(database);
/// }
/// }
/// }
/// ]]>
///
///
public virtual FeedIterator GetDatabaseQueryIterator(
string queryText = null,
string continuationToken = null,
QueryRequestOptions requestOptions = null)
{
QueryDefinition queryDefinition = null;
if (queryText != null)
{
queryDefinition = new QueryDefinition(queryText);
}
return new FeedIteratorInlineCore(
this.GetDatabaseQueryIteratorHelper(
queryDefinition,
continuationToken,
requestOptions),
this.ClientContext);
}
///
/// This method creates a query for databases under an Cosmos DB Account using a SQL statement. It returns a FeedIterator.
///
/// The cosmos SQL query text.
/// The continuation token in the Azure Cosmos DB service.
/// (Optional) The options for the query request.
/// An iterator to go through the databases
/// https://aka.ms/cosmosdb-dot-net-exceptions
///
/// Refer to https://docs.microsoft.com/azure/cosmos-db/sql-query-getting-started for syntax and examples.
///
/// is recommended for single database look-up.
///
///
///
/// Example on how to fully drain the query results.
///
///
///
///
public virtual FeedIterator GetDatabaseQueryStreamIterator(
string queryText = null,
string continuationToken = null,
QueryRequestOptions requestOptions = null)
{
QueryDefinition queryDefinition = null;
if (queryText != null)
{
queryDefinition = new QueryDefinition(queryText);
}
return new FeedIteratorInlineCore(
this.GetDatabaseQueryStreamIterator(
queryDefinition,
continuationToken,
requestOptions),
this.ClientContext);
}
///
/// Creates a new instance of a distributed write transaction.
///
/// An instance of Distributed transaction.
#if PREVIEW
public
#else
internal
#endif
virtual DistributedWriteTransaction CreateDistributedWriteTransaction()
{
return new DistributedWriteTransactionCore(this.ClientContext);
}
///
/// Creates a new instance of a distributed read transaction.
///
/// An instance of .
#if PREVIEW
public
#else
internal
#endif
virtual DistributedReadTransaction CreateDistributedReadTransaction()
{
return new DistributedReadTransactionCore(this.ClientContext);
}
///
/// Send a request for creating a database.
///
/// A database manages users, permissions and a set of containers.
/// Each Azure Cosmos DB Database Account is able to support multiple independent named databases,
/// with the database being the logical container for data.
///
/// Each Database consists of one or more containers, each of which in turn contain one or more
/// documents. Since databases are an administrative resource, the Service Master Key will be
/// required in order to access and successfully complete any action using the User APIs.
///
/// The database properties
/// (Optional) The throughput provisioned for a database in measurement of Request Units per second in the Azure Cosmos DB service.
/// (Optional) A set of options that can be set.
/// (Optional) representing request cancellation.
/// A containing a which wraps a containing the resource record.
/// https://aka.ms/cosmosdb-dot-net-exceptions
/// Request Units
public virtual Task CreateDatabaseStreamAsync(
DatabaseProperties databaseProperties,
int? throughput = null,
RequestOptions requestOptions = null,
CancellationToken cancellationToken = default)
{
if (databaseProperties == null)
{
throw new ArgumentNullException(nameof(databaseProperties));
}
return this.ClientContext.OperationHelperAsync(
operationName: nameof(CreateDatabaseStreamAsync),
containerName: null,
databaseName: databaseProperties.Id,
operationType: OperationType.Create,
requestOptions: requestOptions,
task: (trace) =>
{
this.ClientContext.ValidateResource(databaseProperties.Id);
return this.CreateDatabaseStreamInternalAsync(
databaseProperties,
ThroughputProperties.CreateManualThroughput(throughput),
requestOptions,
trace,
cancellationToken);
},
openTelemetry: new (OpenTelemetryConstants.Operations.CreateDatabase, (response) => new OpenTelemetryResponse(response)));
}
///
/// Removes the DefaultTraceListener which causes locking issues which leads to avability problems.
///
private static void RemoveDefaultTraceListener()
{
if (Core.Trace.DefaultTrace.TraceSource.Listeners.Count > 0)
{
List removeDefaultTraceListeners = new List();
foreach (object traceListnerObject in Core.Trace.DefaultTrace.TraceSource.Listeners)
{
// The TraceSource already has the default trace listener
if (traceListnerObject is DefaultTraceListener defaultTraceListener)
{
removeDefaultTraceListeners.Add(defaultTraceListener);
}
}
// Remove all the default trace listeners
foreach (DefaultTraceListener defaultTraceListener in removeDefaultTraceListeners)
{
Core.Trace.DefaultTrace.TraceSource.Listeners.Remove(defaultTraceListener);
}
}
}
internal virtual async Task GetAccountConsistencyLevelAsync()
{
if (!this.accountConsistencyLevel.HasValue)
{
this.accountConsistencyLevel = await this.DocumentClient.GetDefaultConsistencyLevelAsync();
}
return this.accountConsistencyLevel.Value;
}
internal DatabaseProperties PrepareDatabaseProperties(string id)
{
if (string.IsNullOrWhiteSpace(id))
{
throw new ArgumentNullException(nameof(id));
}
DatabaseProperties databaseProperties = new DatabaseProperties()
{
Id = id
};
this.ClientContext.ValidateResource(databaseProperties.Id);
return databaseProperties;
}
///
/// Send a request for creating a database.
///
/// A database manages users, permissions and a set of containers.
/// Each Azure Cosmos DB Database Account is able to support multiple independent named databases,
/// with the database being the logical container for data.
///
/// Each Database consists of one or more containers, each of which in turn contain one or more
/// documents. Since databases are an administrative resource, the Service Master Key will be
/// required in order to access and successfully complete any action using the User APIs.
///
/// The database properties
/// (Optional) The throughput provisioned for a database in measurement of Request Units per second in the Azure Cosmos DB service.
/// (Optional) A set of options that can be set.
/// (Optional) representing request cancellation.
/// A containing a which wraps a containing the resource record.
/// Request Units
internal virtual Task CreateDatabaseStreamAsync(
DatabaseProperties databaseProperties,
ThroughputProperties throughputProperties,
RequestOptions requestOptions = null,
CancellationToken cancellationToken = default)
{
if (databaseProperties == null)
{
throw new ArgumentNullException(nameof(databaseProperties));
}
return this.ClientContext.OperationHelperAsync(
operationName: nameof(CreateDatabaseStreamAsync),
containerName: null,
databaseName: databaseProperties.Id,
operationType: OperationType.Create,
requestOptions: requestOptions,
task: (trace) =>
{
this.ClientContext.ValidateResource(databaseProperties.Id);
return this.CreateDatabaseStreamInternalAsync(
databaseProperties,
throughputProperties,
requestOptions,
trace,
cancellationToken);
},
openTelemetry: new (OpenTelemetryConstants.Operations.CreateDatabase, (response) => new OpenTelemetryResponse(response)));
}
private async Task CreateDatabaseInternalAsync(
DatabaseProperties databaseProperties,
ThroughputProperties throughputProperties,
RequestOptions requestOptions,
ITrace trace,
CancellationToken cancellationToken)
{
ResponseMessage response = await this.ClientContext.ProcessResourceOperationStreamAsync(
resourceUri: this.DatabaseRootUri,
resourceType: ResourceType.Database,
operationType: OperationType.Create,
requestOptions: requestOptions,
cosmosContainerCore: null,
feedRange: null,
streamPayload: this.ClientContext.SerializerCore.ToStream(databaseProperties),
requestEnricher: (httpRequestMessage) => httpRequestMessage.AddThroughputPropertiesHeader(throughputProperties),
trace,
cancellationToken: cancellationToken);
return this.ClientContext.ResponseFactory.CreateDatabaseResponse(this.GetDatabase(databaseProperties.Id), response);
}
private Task CreateDatabaseStreamInternalAsync(
DatabaseProperties databaseProperties,
ThroughputProperties throughputProperties,
RequestOptions requestOptions,
ITrace trace,
CancellationToken cancellationToken)
{
return this.ClientContext.ProcessResourceOperationAsync(
resourceUri: this.DatabaseRootUri,
resourceType: ResourceType.Database,
operationType: OperationType.Create,
requestOptions: requestOptions,
containerInternal: null,
feedRange: null,
streamPayload: this.ClientContext.SerializerCore.ToStream(databaseProperties),
requestEnricher: (httpRequestMessage) => httpRequestMessage.AddThroughputPropertiesHeader(throughputProperties),
responseCreator: (response) => response,
trace: trace,
cancellationToken: cancellationToken);
}
private FeedIteratorInternal GetDatabaseQueryIteratorHelper(
QueryDefinition queryDefinition,
string continuationToken = null,
QueryRequestOptions requestOptions = null)
{
if (!(this.GetDatabaseQueryStreamIteratorHelper(
queryDefinition,
continuationToken,
requestOptions) is FeedIteratorInternal databaseStreamIterator))
{
throw new InvalidOperationException($"Expected a FeedIteratorInternal.");
}
return new FeedIteratorCore(
databaseStreamIterator,
(response) => this.ClientContext.ResponseFactory.CreateQueryFeedResponse(
responseMessage: response,
resourceType: ResourceType.Database));
}
private FeedIteratorInternal GetDatabaseQueryStreamIteratorHelper(
QueryDefinition queryDefinition,
string continuationToken = null,
QueryRequestOptions requestOptions = null)
{
return new FeedIteratorCore(
clientContext: this.ClientContext,
resourceLink: this.DatabaseRootUri,
resourceType: ResourceType.Database,
queryDefinition: queryDefinition,
continuationToken: continuationToken,
container: null,
options: requestOptions);
}
///
/// Initializes the container by creating the Rntbd
/// connection to all of the backend replica nodes.
///
/// A read-only list containing the database id
/// and their respective container id.
/// An instance of .
internal async Task InitializeContainersAsync(
IReadOnlyList<(string databaseId, string containerId)> containers,
CancellationToken cancellationToken)
{
try
{
List tasks = new ();
foreach ((string databaseId, string containerId) in containers)
{
ContainerInternal container = (ContainerInternal)this.GetContainer(
databaseId,
containerId);
tasks.Add(this.ClientContext.InitializeContainerUsingRntbdAsync(
databaseId: databaseId,
containerLinkUri: container.LinkUri,
cancellationToken: cancellationToken));
}
await Task.WhenAll(tasks);
}
catch
{
this.Dispose();
throw;
}
}
private int IncrementNumberOfClientsCreated()
{
this.IncrementNumberOfActiveClients();
return Interlocked.Increment(ref numberOfClientsCreated);
}
private int IncrementNumberOfActiveClients()
{
return Interlocked.Increment(ref NumberOfActiveClients);
}
private int DecrementNumberOfActiveClients()
{
// In case dispose is called multiple times. Check if at least 1 active client is there
if (NumberOfActiveClients > 0)
{
CosmosDbOperationMeter.RemoveInstanceCount(this.Endpoint);
return Interlocked.Decrement(ref NumberOfActiveClients);
}
return 0;
}
///
/// Dispose of cosmos client
///
public void Dispose()
{
this.Dispose(true);
}
///
/// Dispose of cosmos client
///
/// True if disposing
protected virtual void Dispose(bool disposing)
{
lock (this.disposedLock)
{
if (this.isDisposed == true)
{
return;
}
this.isDisposed = true;
}
this.DisposedDateTimeUtc = DateTime.UtcNow;
if (disposing)
{
this.ClientContext.Dispose();
this.DecrementNumberOfActiveClients();
}
}
}
}