Migrate your application to use the Azure Cosmos DB .NET SDK v3
APPLIES TO: NoSQL
Important
To learn about the Azure Cosmos DB .NET SDK v3, see the Release notes, the .NET GitHub repository, .NET SDK v3 Performance Tips, and the Troubleshooting guide.
This article highlights some of the considerations of upgrading your existing .NET application to the newer Azure Cosmos DB .NET SDK v3 for API for NoSQL. Azure Cosmos DB .NET SDK v3 corresponds to the Microsoft.Azure.Azure Cosmos DB namespace. You can use the information provided in this doc if you're migrating your application from any of the following Azure Cosmos DB .NET SDKs:
- Azure Cosmos DB .NET Framework SDK v2 for API for NoSQL
- Azure Cosmos DB .NET Core SDK v2 for API for NoSQL
The instructions in this article also help you to migrate the following external libraries that are now part of the Azure Cosmos DB .NET SDK v3 for API for NoSQL:
- .NET change feed processor library 2.0
- .NET bulk executor library 1.1 or greater
What's new in the .NET V3 SDK
The v3 SDK contains many usability and performance improvements, including:
- Intuitive programming model naming
- .NET Standard 2.0 **
- Increased performance through stream API support
- Fluent hierarchy that replaces the need for URI factory
- Built-in support for change feed processor library
- Built-in support for bulk operations
- Mockable APIs for easier unit testing
- Transactional batch and Blazor support
- Pluggable serializers
- Scale non-partitioned and autoscale containers
** The SDK targets .NET Standard 2.0 that unifies the existing Azure Cosmos DB .NET Framework and .NET Core SDKs into a single .NET SDK. You can use the .NET SDK in any platform that implements .NET Standard 2.0, including your .NET Framework 4.6.1+ and .NET Core 2.0+ applications.
Most of the networking, retry logic, and lower levels of the SDK remain largely unchanged.
The Azure Cosmos DB .NET SDK v3 is now open source. We welcome any pull requests and will be logging issues and tracking feedback on GitHub. We'll work on taking on any features that will improve customer experience.
Why migrate to the .NET v3 SDK
In addition to the numerous usability and performance improvements, new feature investments made in the latest SDK won't be back ported to older versions. The v2 SDK is currently in maintenance mode. For the best development experience, we recommend always starting with the latest supported version of SDK.
Major name changes from v2 SDK to v3 SDK
The following name changes have been applied throughout the .NET 3.0 SDK to align with the API naming conventions for the API for NoSQL:
DocumentClient
is renamed toCosmosClient
Collection
is renamed toContainer
Document
is renamed toItem
All the resource objects are renamed with additional properties, which, includes the resource name for clarity.
The following are some of the main class name changes:
.NET v2 SDK | .NET v3 SDK |
---|---|
Microsoft.Azure.Documents.Client.DocumentClient |
Microsoft.Azure.Cosmos.CosmosClient |
Microsoft.Azure.Documents.Client.ConnectionPolicy |
Microsoft.Azure.Cosmos.CosmosClientOptions |
Microsoft.Azure.Documents.Client.DocumentClientException |
Microsoft.Azure.Cosmos.CosmosException |
Microsoft.Azure.Documents.Client.Database |
Microsoft.Azure.Cosmos.DatabaseProperties |
Microsoft.Azure.Documents.Client.DocumentCollection |
Microsoft.Azure.Cosmos.ContainerProperties |
Microsoft.Azure.Documents.Client.RequestOptions |
Microsoft.Azure.Cosmos.ItemRequestOptions |
Microsoft.Azure.Documents.Client.FeedOptions |
Microsoft.Azure.Cosmos.QueryRequestOptions |
Microsoft.Azure.Documents.Client.StoredProcedure |
Microsoft.Azure.Cosmos.StoredProcedureProperties |
Microsoft.Azure.Documents.Client.Trigger |
Microsoft.Azure.Cosmos.TriggerProperties |
Microsoft.Azure.Documents.SqlQuerySpec |
Microsoft.Azure.Cosmos.QueryDefinition |
Classes replaced on .NET v3 SDK
The following classes have been replaced on the 3.0 SDK:
Microsoft.Azure.Documents.UriFactory
The Microsoft.Azure.Documents.UriFactory class has been replaced by the fluent design.
Container container = client.GetContainer(databaseName,containerName);
ItemResponse<SalesOrder> response = await this._container.CreateItemAsync(
salesOrder,
new PartitionKey(salesOrder.AccountNumber));
Microsoft.Azure.Documents.Document
Because the .NET v3 SDK allows users to configure a custom serialization engine, there's no direct replacement for the Document
type. When using Newtonsoft.Json (default serialization engine), JObject
can be used to achieve the same functionality. When using a different serialization engine, you can use its base json document type (for example, JsonDocument
for System.Text.Json). The recommendation is to use a C# type that reflects the schema of your items instead of relying on generic types.
Microsoft.Azure.Documents.Resource
There is no direct replacement for Resource
, in cases where it was used for documents, follow the guidance for Document
.
Microsoft.Azure.Documents.AccessCondition
IfNoneMatch
or IfMatch
are now available on the Microsoft.Azure.Cosmos.ItemRequestOptions
directly.
Changes to item ID generation
Item ID is no longer auto populated in the .NET v3 SDK. Therefore, the Item ID must specifically include a generated ID. View the following example:
[JsonProperty(PropertyName = "id")]
public Guid Id { get; set; }
Changed default behavior for connection mode
The SDK v3 now defaults to Direct + TCP connection modes compared to the previous v2 SDK, which defaulted to Gateway + HTTPS connections modes. This change provides enhanced performance and scalability.
Changes to FeedOptions (QueryRequestOptions in v3.0 SDK)
The FeedOptions
class in SDK v2 has now been renamed to QueryRequestOptions
in the SDK v3 and within the class, several properties have had changes in name and/or default value or been removed completely.
.NET v2 SDK | .NET v3 SDK |
---|---|
FeedOptions.MaxDegreeOfParallelism |
QueryRequestOptions.MaxConcurrency - Default value and associated behavior remains the same, operations run client side during parallel query execution will be executed serially with no-parallelism. |
FeedOptions.PartitionKey |
QueryRequestOptions.PartitionKey - Behavior maintained. |
FeedOptions.EnableCrossPartitionQuery |
Removed. Default behavior in SDK 3.0 is that cross-partition queries will be executed without the need to enable the property specifically. |
FeedOptions.PopulateQueryMetrics |
Removed. It is now enabled by default and part of the diagnostics. |
FeedOptions.RequestContinuation |
Removed. It is now promoted to the query methods themselves. |
FeedOptions.JsonSerializerSettings |
Removed. See how to customize serialization for additional information. |
FeedOptions.PartitionKeyRangeId |
Removed. Same outcome can be obtained from using FeedRange as input to the query method. |
FeedOptions.DisableRUPerMinuteUsage |
Removed. |
Constructing a client
The .NET SDK v3 provides a fluent CosmosClientBuilder
class that replaces the need for the SDK v2 URI Factory.
The fluent design builds URLs internally and allows a single Container
object to be passed around instead of a DocumentClient
, DatabaseName
, and DocumentCollection
.
The following example creates a new CosmosClientBuilder
with a strong ConsistencyLevel and a list of preferred locations:
CosmosClientBuilder cosmosClientBuilder = new CosmosClientBuilder(
accountEndpoint: "https://testcosmos.documents.azure.com:443/",
authKeyOrResourceToken: "SuperSecretKey")
.WithConsistencyLevel(ConsistencyLevel.Strong)
.WithApplicationRegion(Regions.EastUS);
CosmosClient client = cosmosClientBuilder.Build();
Exceptions
Where the v2 SDK used DocumentClientException
to signal errors during operations, the v3 SDK uses CosmosException
, which exposes the StatusCode
, Diagnostics
, and other response-related information. All the complete information is serialized when ToString()
is used:
catch (CosmosException ex)
{
HttpStatusCode statusCode = ex.StatusCode;
CosmosDiagnostics diagnostics = ex.Diagnostics;
// store diagnostics optionally with diagnostics.ToString();
// or log the entire error details with ex.ToString();
}
Diagnostics
Where the v2 SDK had Direct-only diagnostics available through the RequestDiagnosticsString
property, the v3 SDK uses Diagnostics
available in all responses and exceptions, which are richer and not restricted to Direct mode. They include not only the time spent on the SDK for the operation, but also the regions the operation contacted:
try
{
ItemResponse<MyItem> response = await container.ReadItemAsync<MyItem>(
partitionKey: new PartitionKey("MyPartitionKey"),
id: "MyId");
TimeSpan elapsedTime = response.Diagnostics.GetElapsedTime();
if (elapsedTime > somePreDefinedThreshold)
{
// log response.Diagnostics.ToString();
IReadOnlyList<(string region, Uri uri)> regions = response.Diagnostics.GetContactedRegions();
}
}
catch (CosmosException cosmosException) {
string diagnostics = cosmosException.Diagnostics.ToString();
TimeSpan elapsedTime = cosmosException.Diagnostics.GetElapsedTime();
IReadOnlyList<(string region, Uri uri)> regions = cosmosException.Diagnostics.GetContactedRegions();
// log cosmosException.ToString()
}
ConnectionPolicy
Some settings in ConnectionPolicy
have been renamed or replaced by CosmosClientOptions
:
.NET v2 SDK | .NET v3 SDK |
---|---|
EnableEndpointDiscovery |
LimitToEndpoint - The value is now inverted, if EnableEndpointDiscovery was being set to true , LimitToEndpoint should be set to false . Before using this setting, you need to understand how it affects the client. |
ConnectionProtocol |
Removed. Protocol is tied to the Mode, either it's Gateway (HTTPS) or Direct (TCP). Direct mode with HTTPS protocol is no longer supported on V3 SDK and the recommendation is to use TCP protocol. |
MediaRequestTimeout |
Removed. Attachments are no longer supported. |
SetCurrentLocation |
CosmosClientOptions.ApplicationRegion can be used to achieve the same effect. |
PreferredLocations |
CosmosClientOptions.ApplicationPreferredRegions can be used to achieve the same effect. |
UserAgentSuffix |
CosmosClientBuilder.ApplicationName can be used to achieve the same effect. |
UseMultipleWriteLocations |
Removed. The SDK automatically detects if the account supports multiple write endpoints. |
Indexing policy
In the indexing policy, it is not possible to configure these properties. When not specified, these properties will now always have the following values:
Property Name | New Value (not configurable) |
---|---|
Kind |
range |
dataType |
String and Number |
See this section for indexing policy examples for including and excluding paths. Due to improvements in the query engine, configuring these properties, even if using an older SDK version, has no impact on performance.
Session token
Where the v2 SDK exposed the session token of a response as ResourceResponse.SessionToken
for cases where capturing the session token was required, because the session token is a header, the v3 SDK exposes that value in the Headers.Session
property of any response.
Timestamp
Where the v2 SDK exposed the timestamp of a document through the Timestamp
property, because Document
is no longer available, users can map the _ts
system property to a property in their model.
OpenAsync
For use cases where OpenAsync()
was being used to warm up the v2 SDK client, CreateAndInitializeAsync
can be used to both create and warm-up a v3 SDK client.
Using the change feed processor APIs directly from the v3 SDK
The v3 SDK has built-in support for the Change Feed Processor APIs, allowing you use the same SDK for building your application and change feed processor implementation. Previously, you had to use a separate change feed processor library.
For more information, see how to migrate from the change feed processor library to the Azure Cosmos DB .NET v3 SDK
Change feed queries
Executing change feed queries on the v3 SDK is considered to be using the change feed pull model. Follow this table to migrate configuration:
.NET v2 SDK | .NET v3 SDK |
---|---|
ChangeFeedOptions.PartitionKeyRangeId |
FeedRange - In order to achieve parallelism reading the change feed FeedRanges can be used. It's no longer a required parameter, you can read the Change Feed for an entire container easily now. |
ChangeFeedOptions.PartitionKey |
FeedRange.FromPartitionKey - A FeedRange representing the desired Partition Key can be used to read the Change Feed for that Partition Key value. |
ChangeFeedOptions.RequestContinuation |
ChangeFeedStartFrom.Continuation - The change feed iterator can be stopped and resumed at any time by saving the continuation and using it when creating a new iterator. |
ChangeFeedOptions.StartTime |
ChangeFeedStartFrom.Time |
ChangeFeedOptions.StartFromBeginning |
ChangeFeedStartFrom.Beginning |
ChangeFeedOptions.MaxItemCount |
ChangeFeedRequestOptions.PageSizeHint - The change feed iterator can be stopped and resumed at any time by saving the continuation and using it when creating a new iterator. |
IDocumentQuery.HasMoreResults |
response.StatusCode == HttpStatusCode.NotModified - The change feed is conceptually infinite, so there could always be more results. When a response contains the HttpStatusCode.NotModified status code, it means there are no new changes to read at this time. You can use that to stop and save the continuation or to temporarily sleep or wait and then call ReadNextAsync again to test for new changes. |
Split handling | It's no longer required for users to handle split exceptions when reading the change feed, splits will be handled transparently without the need of user interaction. |
Using the bulk executor library directly from the V3 SDK
The v3 SDK has built-in support for the bulk executor library, allowing you to use the same SDK for building your application and performing bulk operations. Previously, you were required to use a separate bulk executor library.
For more information, see how to migrate from the bulk executor library to bulk support in Azure Cosmos DB .NET V3 SDK
Customize serialization
The .NET V2 SDK allows setting JsonSerializerSettings in RequestOptions at the operational level used to deserialize the result document:
// .NET V2 SDK
var result = await container.ReplaceDocumentAsync(document, new RequestOptions { JsonSerializerSettings = customSerializerSettings })
The .NET SDK v3 provides a serializer interface to fully customize the serialization engine, or more generic serialization options as part of the client construction.
Customizing the serialization at the operation level can be achieved through the use of Stream APIs:
// .NET V3 SDK
using(Response response = await this.container.ReplaceItemStreamAsync(stream, "itemId", new PartitionKey("itemPartitionKey"))
{
using(Stream stream = response.ContentStream)
{
using (StreamReader streamReader = new StreamReader(stream))
{
// Read the stream and do dynamic deserialization based on type with a custom Serializer
}
}
}
Code snippet comparisons
The following code snippet shows the differences in how resources are created between the .NET v2 and v3 SDKs:
Database operations
Create a database
// Create database with no shared provisioned throughput
DatabaseResponse databaseResponse = await client.CreateDatabaseIfNotExistsAsync(DatabaseName);
Database database = databaseResponse;
DatabaseProperties databaseProperties = databaseResponse;
// Create a database with a shared manual provisioned throughput
string databaseIdManual = new string(DatabaseName + "_SharedManualThroughput");
database = await client.CreateDatabaseIfNotExistsAsync(databaseIdManual, ThroughputProperties.CreateManualThroughput(400));
// Create a database with shared autoscale provisioned throughput
string databaseIdAutoscale = new string(DatabaseName + "_SharedAutoscaleThroughput");
database = await client.CreateDatabaseIfNotExistsAsync(databaseIdAutoscale, ThroughputProperties.CreateAutoscaleThroughput(4000));
Read a database by ID
// Read a database
Console.WriteLine($"{Environment.NewLine} Read database resource: {DatabaseName}");
database = client.GetDatabase(DatabaseName);
Console.WriteLine($"{Environment.NewLine} database { database.Id.ToString()}");
// Read all databases
string findQueryText = "SELECT * FROM c";
using (FeedIterator<DatabaseProperties> feedIterator = client.GetDatabaseQueryIterator<DatabaseProperties>(findQueryText))
{
while (feedIterator.HasMoreResults)
{
FeedResponse<DatabaseProperties> databaseResponses = await feedIterator.ReadNextAsync();
foreach (DatabaseProperties _database in databaseResponses)
{
Console.WriteLine($"{ Environment.NewLine} database {_database.Id.ToString()}");
}
}
}
Delete a database
// Delete a database
await client.GetDatabase(DatabaseName).DeleteAsync();
Console.WriteLine($"{ Environment.NewLine} database {DatabaseName} deleted.");
// Delete all databases in an account
string deleteQueryText = "SELECT * FROM c";
using (FeedIterator<DatabaseProperties> feedIterator = client.GetDatabaseQueryIterator<DatabaseProperties>(deleteQueryText))
{
while (feedIterator.HasMoreResults)
{
FeedResponse<DatabaseProperties> databaseResponses = await feedIterator.ReadNextAsync();
foreach (DatabaseProperties _database in databaseResponses)
{
await client.GetDatabase(_database.Id).DeleteAsync();
Console.WriteLine($"{ Environment.NewLine} database {_database.Id} deleted");
}
}
}
Container operations
Create a container (Autoscale + Time to live with expiration)
private static async Task CreateManualThroughputContainer(Database database)
{
// Set throughput to the minimum value of 400 RU/s manually configured throughput
string containerIdManual = ContainerName + "_Manual";
ContainerResponse container = await database.CreateContainerIfNotExistsAsync(
id: containerIdManual,
partitionKeyPath: partitionKeyPath,
throughput: 400);
}
// Create container with autoscale
private static async Task CreateAutoscaleThroughputContainer(Database database)
{
string autoscaleContainerId = ContainerName + "_Autoscale";
ContainerProperties containerProperties = new ContainerProperties(autoscaleContainerId, partitionKeyPath);
Container container = await database.CreateContainerIfNotExistsAsync(
containerProperties: containerProperties,
throughputProperties: ThroughputProperties.CreateAutoscaleThroughput(autoscaleMaxThroughput: 4000);
}
// Create a container with TTL Expiration
private static async Task CreateContainerWithTtlExpiration(Database database)
{
string containerIdManualwithTTL = ContainerName + "_ManualTTL";
ContainerProperties properties = new ContainerProperties
(id: containerIdManualwithTTL,
partitionKeyPath: partitionKeyPath);
properties.DefaultTimeToLive = (int)TimeSpan.FromDays(1).TotalSeconds; //expire in 1 day
ContainerResponse containerResponse = await database.CreateContainerIfNotExistsAsync(containerProperties: properties);
ContainerProperties returnedProperties = containerResponse;
}
Read container properties
private static async Task ReadContainerProperties(Database database)
{
string containerIdManual = ContainerName + "_Manual";
Container container = database.GetContainer(containerIdManual);
ContainerProperties containerProperties = await container.ReadContainerAsync();
}
Delete a container
private static async Task DeleteContainers(Database database)
{
string containerIdManual = ContainerName + "_Manual";
// Delete a container
await database.GetContainer(containerIdManual).DeleteContainerAsync();
// Delete all CosmosContainer resources for a database
using (FeedIterator<ContainerProperties> feedIterator = database.GetContainerQueryIterator<ContainerProperties>())
{
while (feedIterator.HasMoreResults)
{
foreach (ContainerProperties _container in await feedIterator.ReadNextAsync())
{
await database.GetContainer(_container.Id).DeleteContainerAsync();
Console.WriteLine($"{Environment.NewLine} deleted container {_container.Id}");
}
}
}
}
Item and query operations
Create an item
private static async Task CreateItemAsync(Container container)
{
// Create a SalesOrder POCO object
SalesOrder salesOrder1 = GetSalesOrderSample("Account1", "SalesOrder1");
ItemResponse<SalesOrder> response = await container.CreateItemAsync(salesOrder1,
new PartitionKey(salesOrder1.AccountNumber));
}
private static async Task RunBasicOperationsOnDynamicObjects(Container container)
{
// Dynamic Object
dynamic salesOrder = new
{
id = "SalesOrder5",
AccountNumber = "Account1",
PurchaseOrderNumber = "PO18009186470",
OrderDate = DateTime.UtcNow,
Total = 5.95,
};
Console.WriteLine("\nCreating item");
ItemResponse<dynamic> response = await container.CreateItemAsync<dynamic>(
salesOrder, new PartitionKey(salesOrder.AccountNumber));
dynamic createdSalesOrder = response.Resource;
}
Read all the items in a container
private static async Task ReadAllItems(Container container)
{
// Read all items in a container
List<SalesOrder> allSalesForAccount1 = new List<SalesOrder>();
using (FeedIterator<SalesOrder> resultSet = container.GetItemQueryIterator<SalesOrder>(
queryDefinition: null,
requestOptions: new QueryRequestOptions()
{
PartitionKey = new PartitionKey("Account1"),
MaxItemCount = 5
}))
{
while (resultSet.HasMoreResults)
{
FeedResponse<SalesOrder> response = await resultSet.ReadNextAsync();
SalesOrder salesOrder = response.First();
Console.WriteLine($"\n1.3.1 Account Number: {salesOrder.AccountNumber}; Id: {salesOrder.Id}");
allSalesForAccount1.AddRange(response);
}
}
}
Query items
Changes to SqlQuerySpec (QueryDefinition in v3.0 SDK)
The SqlQuerySpec
class in SDK v2 has now been renamed to QueryDefinition
in the SDK v3.
SqlParameterCollection
and SqlParameter
has been removed. Parameters are now added to the QueryDefinition
with a builder model using QueryDefinition.WithParameter
. Users can access the parameters with QueryDefinition.GetQueryParameters
private static async Task QueryItems(Container container)
{
// Query for items by a property other than Id
QueryDefinition queryDefinition = new QueryDefinition(
"select * from sales s where s.AccountNumber = @AccountInput")
.WithParameter("@AccountInput", "Account1");
List<SalesOrder> allSalesForAccount1 = new List<SalesOrder>();
using (FeedIterator<SalesOrder> resultSet = container.GetItemQueryIterator<SalesOrder>(
queryDefinition,
requestOptions: new QueryRequestOptions()
{
PartitionKey = new PartitionKey("Account1"),
MaxItemCount = 1
}))
{
while (resultSet.HasMoreResults)
{
FeedResponse<SalesOrder> response = await resultSet.ReadNextAsync();
SalesOrder sale = response.First();
Console.WriteLine($"\n Account Number: {sale.AccountNumber}; Id: {sale.Id};");
allSalesForAccount1.AddRange(response);
}
}
}
Delete an item
private static async Task DeleteItemAsync(Container container)
{
ItemResponse<SalesOrder> response = await container.DeleteItemAsync<SalesOrder>(
partitionKey: new PartitionKey("Account1"), id: "SalesOrder3");
}
Change feed query
private static async Task QueryChangeFeedAsync(Container container)
{
FeedIterator<SalesOrder> iterator = container.GetChangeFeedIterator<SalesOrder>(ChangeFeedStartFrom.Beginning(), ChangeFeedMode.Incremental);
string continuation = null;
while (iterator.HasMoreResults)
{
FeedResponse<SalesOrder> response = await iteratorForTheEntireContainer.ReadNextAsync();
if (response.StatusCode == HttpStatusCode.NotModified)
{
// No new changes
continuation = response.ContinuationToken;
break;
}
else
{
// Process the documents in response
}
}
}
Next steps
- Build a Console app to manage Azure Cosmos DB for NoSQL data using the v3 SDK
- Learn more about what you can do with the v3 SDK
- Trying to do capacity planning for a migration to Azure Cosmos DB?
- If all you know is the number of vcores and servers in your existing database cluster, read about estimating request units using vCores or vCPUs
- If you know typical request rates for your current database workload, read about estimating request units using Azure Cosmos DB capacity planner