Migrate your application to use the Azure Cosmos DB .NET SDK v3



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 Core (SQL) API. Azure Cosmos DB .NET SDK v3 corresponds to the Microsoft.Azure.Cosmos namespace. You can use the information provided in this doc if you are migrating your application from any of the following Azure Cosmos DB .NET SDKs:

  • Azure Cosmos DB .NET Framework SDK v2 for SQL API
  • Azure Cosmos DB .NET Core SDK v2 for SQL API

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 Core (SQL) API:

  • .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
  • Mockabale 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 will not be back ported to older versions.

While there are no immediate plans to retire support for the 2.0 SDKs, the SDKs will be replaced by newer versions in the future and the SDK will go into 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 Core (SQL) API:

  • DocumentClient is renamed to CosmosClient
  • Collection is renamed to Container
  • Document is renamed to Item

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:

Microsoft.Azure.Documents.Client.DocumentClient Microsoft.Azure.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

Classes replaced on .NET v3 SDK

The following classes have been replaced on the 3.0 SDK:

  • Microsoft.Azure.Documents.UriFactory

  • Microsoft.Azure.Documents.Document

  • Microsoft.Azure.Documents.Resource

The Microsoft.Azure.Documents.UriFactory class has been replaced by the fluent design. The fluent design builds URLs internally and allows a single Container object to be passed around instead of a DocumentClient, DatabaseName, and DocumentCollection.

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.

FeedOptions.MaxDegreeOfParallelism has been renamed to QueryRequestOptions.MaxConcurrency and default value and associated behavior remains the same, operations run client side during parallel query execution will be executed serially with no-parallelism.

FeedOptions.EnableCrossPartitionQuery has been removed and the default behavior in SDK 3.0 is that cross-partition queries will be executed without the need to enable the property specifically.

FeedOptions.PopulateQueryMetrics is enabled by default with the results being present in the diagnostics property of the response.

FeedOptions.RequestContinuation has now been promoted to the query methods themselves.

The following properties have been removed:

  • FeedOptions.DisableRUPerMinuteUsage

  • FeedOptions.EnableCrossPartitionQuery

  • FeedOptions.JsonSerializerSettings

  • FeedOptions.PartitionKeyRangeId

  • FeedOptions.PopulateQueryMetrics

Constructing a client

The .NET SDK v3 provides a fluent CosmosClientBuilder class that replaces the need for the SDK v2 URI Factory.

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")
CosmosClient client = cosmosClientBuilder.Build();

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

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

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}");

Query items

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>(
        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};");

Delete an item

private static async Task DeleteItemAsync(Container container)
    ItemResponse<SalesOrder> response = await container.DeleteItemAsync<SalesOrder>(
        partitionKey: new PartitionKey("Account1"), id: "SalesOrder3");

Next steps