Back to Blog
3 min read

Working with Azure Cosmos DB SDK v3 for .NET

Azure Cosmos DB continues to be a popular choice for globally distributed applications. The .NET SDK v3, released last year, brought significant improvements in performance and usability. Let me share some patterns I have been using.

Setting Up the SDK

dotnet add package Microsoft.Azure.Cosmos

Creating the Cosmos Client

The SDK v3 uses a simplified client model:

using Microsoft.Azure.Cosmos;

public class CosmosService
{
    private readonly CosmosClient _cosmosClient;
    private readonly Container _container;

    public CosmosService(string connectionString)
    {
        var options = new CosmosClientOptions
        {
            SerializerOptions = new CosmosSerializationOptions
            {
                PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase
            },
            ConnectionMode = ConnectionMode.Direct
        };

        _cosmosClient = new CosmosClient(connectionString, options);
        _container = _cosmosClient.GetContainer("MyDatabase", "MyContainer");
    }
}

CRUD Operations

Here is a complete example of basic operations:

public class Product
{
    public string Id { get; set; }
    public string Category { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

public class ProductRepository
{
    private readonly Container _container;

    public ProductRepository(Container container)
    {
        _container = container;
    }

    // Create
    public async Task<Product> CreateAsync(Product product)
    {
        product.Id = Guid.NewGuid().ToString();
        var response = await _container.CreateItemAsync(
            product,
            new PartitionKey(product.Category));
        return response.Resource;
    }

    // Read
    public async Task<Product> GetAsync(string id, string category)
    {
        try
        {
            var response = await _container.ReadItemAsync<Product>(
                id,
                new PartitionKey(category));
            return response.Resource;
        }
        catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
        {
            return null;
        }
    }

    // Update
    public async Task<Product> UpdateAsync(Product product)
    {
        var response = await _container.UpsertItemAsync(
            product,
            new PartitionKey(product.Category));
        return response.Resource;
    }

    // Delete
    public async Task DeleteAsync(string id, string category)
    {
        await _container.DeleteItemAsync<Product>(
            id,
            new PartitionKey(category));
    }
}

Querying with LINQ

SDK v3 has excellent LINQ support:

public async Task<List<Product>> GetProductsByCategoryAsync(string category)
{
    var query = _container.GetItemLinqQueryable<Product>()
        .Where(p => p.Category == category && p.Price > 10)
        .OrderBy(p => p.Name)
        .ToFeedIterator();

    var results = new List<Product>();
    while (query.HasMoreResults)
    {
        var response = await query.ReadNextAsync();
        results.AddRange(response);
    }
    return results;
}

Using SQL Queries

For complex queries, you can use SQL directly:

public async Task<List<Product>> SearchProductsAsync(string searchTerm)
{
    var queryDefinition = new QueryDefinition(
        "SELECT * FROM c WHERE CONTAINS(c.name, @searchTerm)")
        .WithParameter("@searchTerm", searchTerm);

    var query = _container.GetItemQueryIterator<Product>(queryDefinition);

    var results = new List<Product>();
    while (query.HasMoreResults)
    {
        var response = await query.ReadNextAsync();
        results.AddRange(response);
    }
    return results;
}

Batch Operations

For better performance with multiple operations:

public async Task CreateBatchAsync(List<Product> products, string category)
{
    var batch = _container.CreateTransactionalBatch(new PartitionKey(category));

    foreach (var product in products)
    {
        batch.CreateItem(product);
    }

    var response = await batch.ExecuteAsync();

    if (!response.IsSuccessStatusCode)
    {
        throw new Exception($"Batch operation failed: {response.StatusCode}");
    }
}

Request Units Monitoring

Always monitor your RU consumption:

var response = await _container.CreateItemAsync(product, new PartitionKey(product.Category));
Console.WriteLine($"Request charge: {response.RequestCharge} RUs");

Best Practices

  1. Reuse the CosmosClient - It is thread-safe and expensive to create
  2. Choose the right partition key - This is crucial for performance and cost
  3. Use Direct mode - For better performance in production
  4. Enable Application Insights - For monitoring and diagnostics

Cosmos DB SDK v3 provides a robust foundation for building scalable applications with global distribution capabilities.

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.