The practical answer: a useful Azure Cosmos DB repository should make the partition key, continuation token, request charge, and optimistic concurrency visible. A generic CRUD abstraction that hides those concepts may look clean, but it also makes expensive cross-partition queries and lost updates much easier to ship.

This updated guide builds a partition-aware repository for Azure Cosmos DB for NoSQL with ASP.NET Core and .NET 10. It uses one long-lived CosmosClient, passwordless authentication, point reads, parameterized queries, server continuation tokens, ETags, cancellation, and useful diagnostics. The original working .NET 6 demo, screenshots, code, and GitHub repository are preserved later as a clearly marked historical walkthrough.

When a Cosmos DB repository helps—and when it hurts

A repository is valuable when it expresses application-specific access patterns, centralizes consistent error handling, and keeps controllers independent from the SDK. It becomes harmful when a generic interface pretends that Cosmos DB behaves like a relational table.

  • Keep it domain-specific. IDepartmentRepository communicates more than IRepository<T>.
  • Require the partition key. A point read needs both id and the partition-key value. Do not silently replace it with a query.
  • Return one query page. A web API should not drain an unbounded feed into memory.
  • Expose operational signals. Request charge and continuation state affect cost and API behavior.
  • Preserve Cosmos semantics. ETags, 404, 409, 412, and 429 are meaningful outcomes, not generic database errors.

If you first want the general abstraction and dependency-injection background, see Generic Repository Pattern in C#. The rest of this guide deliberately narrows that abstraction for Cosmos DB.

Design a partition-aware contract

The example stores departments in a container whose partition-key path is /location. That choice is part of the data model: every point operation accepts a location, and every location-scoped query sends it in QueryRequestOptions.PartitionKey.

using Newtonsoft.Json;

public sealed class Department
{
    [JsonProperty("id")]
    public required string Id { get; init; }

    [JsonProperty("name")]
    public required string Name { get; init; }

    // Container partition-key path: /location
    [JsonProperty("location")]
    public required string Location { get; init; }

    [JsonProperty("_etag")]
    public string? ETag { get; init; }
}

The repository returns the RU charge with each operation. The query result also carries the opaque continuation token that the caller can send back for the next page.

public sealed record CosmosItemResult<T>(T Item, double RequestCharge);

public sealed record CosmosPage<T>(
    IReadOnlyList<T> Items,
    string? ContinuationToken,
    double RequestCharge);

public interface IDepartmentRepository
{
    Task<CosmosItemResult<Department>?> GetAsync(
        string id,
        string location,
        CancellationToken cancellationToken);

    Task<CosmosItemResult<Department>> CreateAsync(
        Department department,
        CancellationToken cancellationToken);

    Task<CosmosItemResult<Department>> ReplaceAsync(
        Department department,
        CancellationToken cancellationToken);

    Task<bool> DeleteAsync(
        string id,
        string location,
        string? etag,
        CancellationToken cancellationToken);

    Task<CosmosPage<Department>> ListByLocationAsync(
        string location,
        int pageSize,
        string? continuationToken,
        CancellationToken cancellationToken);
}

Register CosmosClient securely

Install the current v3 SDK and authenticate with Microsoft Entra ID. The Cosmos SDK currently requires a direct, secure Newtonsoft.Json dependency even when an application uses System.Text.Json elsewhere.

dotnet add package Microsoft.Azure.Cosmos --version 3.*
dotnet add package Azure.Identity --version 1.*
dotnet add package Newtonsoft.Json --version 13.0.4

Configuration contains resource names and the account endpoint, but no account key:

{
  "Cosmos": {
    "AccountEndpoint": "https://your-account.documents.azure.com:443/",
    "DatabaseName": "staff",
    "DepartmentContainerName": "departments"
  }
}
using System.ComponentModel.DataAnnotations;

public sealed class CosmosOptions
{
    [Required, Url]
    public required string AccountEndpoint { get; init; }

    [Required]
    public required string DatabaseName { get; init; }

    [Required]
    public required string DepartmentContainerName { get; init; }
}

Register one CosmosClient for the application lifetime. It is thread-safe and expensive to initialize. Local development can use Azure CLI or Visual Studio credentials; an Azure-hosted app should use its managed identity. Grant that identity only the required Cosmos DB data-plane role and scope. An Azure Resource Manager role such as Contributor does not automatically grant item access.

using Azure.Identity;
using Microsoft.Azure.Cosmos;
using Microsoft.Extensions.Options;

builder.Services
    .AddOptions<CosmosOptions>()
    .BindConfiguration("Cosmos")
    .ValidateDataAnnotations()
    .ValidateOnStart();

builder.Services.AddSingleton(sp =>
{
    CosmosOptions settings = sp
        .GetRequiredService<IOptions<CosmosOptions>>().Value;

    var credential = new DefaultAzureCredential(
        new DefaultAzureCredentialOptions
        {
            ExcludeInteractiveBrowserCredential = true
        });

    var clientOptions = new CosmosClientOptions
    {
        ApplicationName = "DotNetCoder.StaffApi",
        ConnectionMode = ConnectionMode.Direct,
        SerializerOptions = new CosmosSerializationOptions
        {
            PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase
        }
    };

    return new CosmosClient(
        settings.AccountEndpoint,
        credential,
        clientOptions);
});

builder.Services.AddSingleton<IDepartmentRepository,
    CosmosDepartmentRepository>();

Provision the database, container, throughput, indexing policy, and RBAC assignments with infrastructure as code. Avoid calling CreateDatabaseIfNotExistsAsync or CreateContainerIfNotExistsAsync on every request; those are control-plane operations and unnecessary metadata calls.

Implement point operations and paged queries

The repository keeps a lightweight Container reference. A point read is the correct operation when both identity components are known. It is cheaper and more predictable than querying by id.

using System.Net;
using Microsoft.Azure.Cosmos;
using Microsoft.Extensions.Options;

public sealed class CosmosDepartmentRepository : IDepartmentRepository
{
    private readonly Container _container;

    public CosmosDepartmentRepository(
        CosmosClient client,
        IOptions<CosmosOptions> options)
    {
        CosmosOptions settings = options.Value;
        _container = client.GetContainer(
            settings.DatabaseName,
            settings.DepartmentContainerName);
    }

    public async Task<CosmosItemResult<Department>?> GetAsync(
        string id,
        string location,
        CancellationToken cancellationToken)
    {
        try
        {
            ItemResponse<Department> response =
                await _container.ReadItemAsync<Department>(
                    id,
                    new PartitionKey(location),
                    cancellationToken: cancellationToken);

            return new(response.Resource, response.RequestCharge);
        }
        catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
        {
            return null;
        }
    }

    public async Task<CosmosItemResult<Department>> CreateAsync(
        Department department,
        CancellationToken cancellationToken)
    {
        ItemResponse<Department> response =
            await _container.CreateItemAsync(
                department,
                new PartitionKey(department.Location),
                cancellationToken: cancellationToken);

        return new(response.Resource, response.RequestCharge);
    }

    public async Task<CosmosItemResult<Department>> ReplaceAsync(
        Department department,
        CancellationToken cancellationToken)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(department.ETag);

        ItemResponse<Department> response =
            await _container.ReplaceItemAsync(
                department,
                department.Id,
                new PartitionKey(department.Location),
                new ItemRequestOptions
                {
                    IfMatchEtag = department.ETag
                },
                cancellationToken);

        return new(response.Resource, response.RequestCharge);
    }

    public async Task<bool> DeleteAsync(
        string id,
        string location,
        string? etag,
        CancellationToken cancellationToken)
    {
        try
        {
            await _container.DeleteItemAsync<Department>(
                id,
                new PartitionKey(location),
                new ItemRequestOptions { IfMatchEtag = etag },
                cancellationToken);

            return true;
        }
        catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
        {
            return false;
        }
    }

    public async Task<CosmosPage<Department>> ListByLocationAsync(
        string location,
        int pageSize,
        string? continuationToken,
        CancellationToken cancellationToken)
    {
        int boundedPageSize = Math.Clamp(pageSize, 1, 100);

        var query = new QueryDefinition(
            "SELECT * FROM d WHERE d.location = @location " +
            "ORDER BY d.name")
            .WithParameter("@location", location);

        using FeedIterator<Department> iterator =
            _container.GetItemQueryIterator<Department>(
                query,
                continuationToken,
                new QueryRequestOptions
                {
                    PartitionKey = new PartitionKey(location),
                    MaxItemCount = boundedPageSize
                });

        if (!iterator.HasMoreResults)
        {
            return new([], null, 0);
        }

        FeedResponse<Department> response =
            await iterator.ReadNextAsync(cancellationToken);

        return new(
            response.ToList(),
            response.ContinuationToken,
            response.RequestCharge);
    }
}

The old sample accepted a raw SQL fragment and interpolated it into SELECT * FROM item WHERE .... That is both unsafe and impossible to govern. The updated repository owns a parameterized query, limits the page size, scopes it to one logical partition, and returns only one service page. Keep continuation tokens opaque; clients should return them unchanged.

Protect updates with ETags

Every item has a server-managed _etag. Passing that value as IfMatchEtag makes a replace or delete conditional. If another request has already changed the item, Cosmos DB returns HTTP 412 Precondition Failed instead of silently overwriting the newer version.

Do not catch every CosmosException and throw InvalidOperationException(ex.Message). That destroys status codes, diagnostics, substatus information, and the original stack. Catch only when the application can translate a known outcome; otherwise allow the original exception to reach centralized handling.

Map repository results to HTTP

The API boundary should translate repository outcomes into explicit HTTP responses. The example below returns the ETag and RU charge as headers so concurrency and cost stay observable without changing the response body.

using System.Net;
using Microsoft.Azure.Cosmos;

app.MapGet("/departments/{location}/{id}", async (
    string location,
    string id,
    IDepartmentRepository repository,
    HttpResponse response,
    CancellationToken cancellationToken) =>
{
    CosmosItemResult<Department>? result =
        await repository.GetAsync(id, location, cancellationToken);

    if (result is null)
    {
        return Results.NotFound();
    }

    response.Headers.ETag = result.Item.ETag;
    response.Headers["x-cosmos-request-charge"] =
        result.RequestCharge.ToString("0.##");

    return Results.Ok(result.Item);
});

app.MapPut("/departments/{location}/{id}", async (
    string location,
    string id,
    Department input,
    IDepartmentRepository repository,
    HttpResponse response,
    CancellationToken cancellationToken) =>
{
    if (input.Id != id || input.Location != location)
    {
        return Results.BadRequest(
            "Route values must match the document identity.");
    }

    try
    {
        CosmosItemResult<Department> result =
            await repository.ReplaceAsync(input, cancellationToken);

        response.Headers.ETag = result.Item.ETag;
        response.Headers["x-cosmos-request-charge"] =
            result.RequestCharge.ToString("0.##");

        return Results.Ok(result.Item);
    }
    catch (CosmosException ex)
        when (ex.StatusCode == HttpStatusCode.PreconditionFailed)
    {
        return Results.Conflict(new
        {
            message = "The item changed after it was read. " +
                      "Reload it and retry your update."
        });
    }
});

The SDK already retries many 429 throttling responses according to its retry policy. Avoid layering an unlimited retry loop around it. Log slow or failed operations with CosmosException.ToString() or response.Diagnostics.ToString(); do not parse the diagnostics string because its format is not a stable contract.

Production checklist

  • Partition from access patterns. Confirm that the partition key has adequate cardinality and distributes storage and RU load.
  • Prefer point reads. When you know id and partition key, call ReadItemAsync rather than query by ID.
  • Bound every query. Parameterize values, set the partition key when possible, limit page size, and return continuation tokens.
  • Use one client. Keep a single CosmosClient per account for the application lifetime and place compute in the same Azure region when practical.
  • Use passwordless access. Prefer managed identity and least-privilege Cosmos DB data-plane RBAC. If a legacy workload still uses a key, keep it outside configuration files; the Azure Key Vault guide explains that secret boundary.
  • Use ETags. Require the version read by the client before replace or delete, then surface 412 as a concurrency conflict.
  • Measure RUs and latency. Capture RequestCharge and log diagnostics for slow or failed operations.
  • Model transactional boundaries. Multi-item transactional batches work only inside one logical partition.
  • Provision separately. Manage accounts, databases, containers, throughput, indexes, network rules, and role assignments through deployment automation.
  • Test against the emulator and Azure. Unit-test application decisions, then run integration tests for partition keys, continuation tokens, conflicts, and throttling behavior.

Historical .NET 6 demo

Historical note: the following walkthrough is the original October 2023 .NET 6 sample. It worked at the time and its screenshots, code, and GitHub repository remain useful for understanding the first implementation. It uses account keys, multiple generic repositories, raw filter fragments, and older registration code; treat it as historical context, not the recommended production design above.

Creating an Azure Cosmos DB account

1. Type Cosmos DB into the Azure portal search bar and clicking
+ Create and choose Azure Cosmos DB for NoSQL as
shown in the following screenshot.

Creating azure cosmos db account using azure portal

2. In the Create Azure Cosmos DB Account page, enter the basic settings for the new Azure Cosmos DB account as
shown in the following screenshot.

create azure cosmos db account

3. Select Review + create.

4.  Navigate to Azure Cosmos DB Account  and select the Keys menu. Copy
the URI and PRIMARY KEY values, which we need to  connect
to the Cosmos DB account from our application.

 URI and PRIMARY KEY values

Creating a class library project and adding Models

Our sample relational database for storing information about staff contain the following entities that will be stored in  our azure cosmos db containers, as shown in the diagram below.

Models

1. Open Visual Studio 2022 and click the Create a new project button.

2. Select the Class library project and click the Next button.

3. Enter CS.Staff.Models in the Project name textbox and Staff in the Solution name and click the Next button.

4. Select .NET 6.0 as the version of the Framework to use and click the Create button.

5. Right-click theCS.Staff.Modelsproject and add a new class file named Department.

namespace CS.Staff.Models
{
    public class Department
    {
        [JsonProperty(PropertyName = "id")]
        public string Id { get; set; } 
        public string Name { get; set; } 
        public string Location { get; set; }
        public List<Employee> Employees { get; set; } 
        [JsonProperty("_etag")]
        public string Etag { get; set; }
    }
}

6. Right-click theCS.Staff.Modelsproject and add a new class file named Employee.

namespace CS.Staff.Models
{
    public class Employee
    {
        [JsonProperty(PropertyName = "id")]
        public string Id { get; set; } 
        public string FirstName { get;set; } 
        public string LastName { get; set; } 
        public string Job { get; set; }
        public string Email { get; set; }
        public DateTime HireDate { get; set; } 
        public decimal Salary { get; set; }
        public List<Project> Projects { get; set; } 
        [JsonProperty("_etag")]
        public string Etag { get; set; }
    }
}

7. Right-click theCS.Staff.Modelsproject and add a new class file named Project.

namespace CS.Staff.Models
{
    public class Project
    {
        [JsonProperty(PropertyName = "id")]
        public string Id { get; set; } 
        public DateTime StartDate { get; set; } 
        public DateTime EndDate { get; set; }
        [JsonProperty("_etag")]
        public string Etag { get; set; }
    }
}

The id field is mandatory field for all entities. 

Creating a Database and Containers

1. Navigate to the created Azure Cosmos DB account and Select Data Explorer and then select New Container.

2. Enter the settings for the Departments container as shown in the following screenshot.

Create department table

We use  /Name  as the partition key in the Departments container.

3. Add the Employees container, as shown in the screenshot below.

Create employee container

We use  /Email  as the partition key in the Employees container.

4. Add the Projects container, as shown in the screenshot below.

Azure Cosmos DB repository

We use  /Id  as the partition key in the Projects container.

Creating a class library and implementing the Repository pattern

The Repository pattern is a Domain-Driven Design pattern that provides an abstraction of data that separates the data layer from the rest of the application.

1. Right-click the solution and select the Add, New Project option from the menu.

2. Select the Class library project and click the Next button.

3. Enter CS.Staff.Repositories in the Project name textbox and click the Next button.

4. Select .NET 6.0 as the version of the Framework to use and click the Create button.

project now

5. Install the following NuGet package :

  • Microsoft.Azure.Cosmos

Implementing the Repository pattern

1. Right click the CS.Staff.Repositories project and add a new folder named Interfaces, and add an interface file named IBaseRepository<TItem>

namespace CS.Staff.Repositories.Interfaces
{
    public interface IBaseRepository<TItem> where TItem : class 
    {
        Task<IEnumerable<TItem>> GetItemsAsync(string filter); 
        Task<TItem> FindItemAsync(string id, string partionKey);
        Task<TItem> AddItemAsync(TItem item, string partitionKey);
        Task<TItem> UpdateItemAsync(TItem item, string id, string etag, string partitionKey);
        Task<bool> RemoveItemAsync(string id, string partitionKey);
    }
}

The generic IBaseRepository is common interface for working with any of objects.

2. Add a new class file named BaseRepository<TItem> to the project, that implements the IBaseRepository<TItem> interface.

namespace CS.Staff.Repositories
{
    public class BaseRepository<TItem> : IBaseRepository<TItem> where TItem : class
    {
        private readonly Container container;
        public BaseRepository(CosmosClient cosmosClient, string databaseId, string containerId)
        {
            container = cosmosClient.GetContainer(databaseId, containerId);
        }
        public async Task<TItem> AddItemAsync(TItem item, string partitionKey)
        {
            return await container.CreateItemAsync<TItem>(item, new PartitionKey(partitionKey)).ConfigureAwait(false); 
        }
        public async Task<TItem> FindItemAsync(string id, string partionKey)
        {
            try
            {
                ItemResponse<TItem> item = await container.ReadItemAsync<TItem>(id, new PartitionKey(partionKey)).ConfigureAwait(false);
                return item;
            } catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
            {
                return null;
            }
        }
        public async Task<IEnumerable<TItem>> GetItemsAsync(string filter)
        {
            filter = string.IsNullOrEmpty(filter) ? "select * from item" : $"select * from item where {filter}";
            var filteredFeed = container.GetItemQueryIterator<TItem>(new QueryDefinition(filter));
            List<TItem> items = new();
            while (filteredFeed.HasMoreResults)
            {
                var response = await filteredFeed.ReadNextAsync().ConfigureAwait(false);
                items.AddRange(response.ToList());
            }
            return items;
        }
        public async Task<TItem> UpdateItemAsync(TItem item, string id, string etag, string partitionKey)
        {
            try
            {
                return await container.ReplaceItemAsync<TItem>(item, id, new PartitionKey(partitionKey), new ItemRequestOptions { IfMatchEtag = etag }).ConfigureAwait(false);
            }
            catch(CosmosException ex)
            {
                throw new InvalidOperationException(ex.Message);
            }
        }
        public async Task<bool> RemoveItemAsync(string id, string partitionKey)
        {
            try
            {
                ItemResponse<TItem> item = await container.DeleteItemAsync<TItem>(id, new PartitionKey(partitionKey)).ConfigureAwait(false);
                return true;
            }
            catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
            {
                return false;
            }
        }
    }
}

Since we don’t need to access the original context, we use  ConfigureAwait(false) in all awaited methods.

ItemResponse<TItem> item = await container.ReadItemAsync<TItem>(id, new PartitionKey(partionKey)).ConfigureAwait(false);

We have declared a private variable of type Microsoft.Azure.Cosmos.Container at the top that points to a corresponding container based on the containerId parameter.

private readonly Container container;

The BaseRepository constructor receives an instance of CosmosClientusing Dependency Injection, and then we initialize the private member container withGetContainer(databaseId, containerId)

public BaseRepository(CosmosClient cosmosClient, string databaseId, string containerId)
{
    container = cosmosClient.GetContainer(databaseId, containerId);
}

The other five methods are self-explanatory code that wraps around the Azure Cosmos container.

3.  Right click the  Interfaces folder and add three interface files named IDepartmentRepositoryIEmployeeRepository and IProjectRepository (see below).

namespace CS.Staff.Repositories.Interfaces
{
    public interface IDepartmentRepository :IBaseRepository<Department>
    {
    }
}
namespace CS.Staff.Repositories.Interfaces
{
    public interface IEmployeeRepository :IBaseRepository<Employee>
    {
    }
}

These interfaces do not add any functionality beyond what’s provided in the IBaseRepository.

4. Right click the CS.Staff.Repositories project and add a new class file named DatabaseSettings as shown below.

namespace CS.Staff.Repositories
{
    public class DatabaseSettings
    {
        public string DatabaseId { get; set; } 
        public string ContainerId { get; set; }
    }

This class contains information related to the container and the database.

5. Right click the CS.Staff.Repositories project and add a new class file named DepartmentRepository as shown below.

namespace CS.Staff.Repositories
{
    public class DepartmentRepository : BaseRepository<Department>, IDepartmentRepository
    {
        public DepartmentRepository(CosmosClient cosmosClient, DatabaseSettings settings) 
            : base(cosmosClient, settings.DatabaseId, settings.ContainerId)
        {
        }
    }
}

The DepartmentRepository implements the IBaseRepository and the IDepartmentRepository.

It receives CosmosClientinstance and DatabaseSettings instance using Dependency Injection.

3. Similarly, dd two class files called EmployeeRepository and ProjectRepository (see below).

namespace CS.Staff.Repositories
{
    public class EmployeeRepository :BaseRepository<Employee>, IEmployeeRepository
    {
        public EmployeeRepository(CosmosClient cosmosClient, DatabaseSettings settings) 
            : base(cosmosClient, settings.DatabaseId, settings.ContainerId)
        {
        }
    }
}
namespace CS.Staff.Repositories
{
    public class ProjectRepository : BaseRepository<Project>, IProjectRepository
    {
        public ProjectRepository(CosmosClient cosmosClient, DatabaseSettings settings)
            : base(cosmosClient, settings.DatabaseId, settings.ContainerId)
        {
        }
    }
}

Creating ASP.NET Core API

1. Create an ASP.NET Core Web API project using Visual Studio with the name CS.Staff.ApiApp

create api app

No we need to Add our azure cosmos db models and repositories packages to our Api App.

1. In Solution Explorer, right click the CS.Staff.ApiApp project’s Dependencies node, and select Add Project Reference.

 In the Reference Manager dialog, select the CS.Staff.Repositories project, and select OK.

2. Open the appsettings.Development.json file and add the information we need to connect
to the Cosmos DB account from our app URI , Primary Key , and container names .

{
  "AccountEndpoint": "YOU ACCOUNT ENDPOINT",
  "AuthKey": "YOUR PRIMARY KEY",
  "DatabaseId": "cs-staff-database",
  "DepartmentContainer": "Departments",
  "EmployeeContainer": "Employees",
  "ProjectContainer":  "Projects"
}

3. Add a project folder named Extensions and add a new class file called RepositoryExtensions

namespace CS.Staff.ApiApp.Extensions
{
    public static class RepositoryExtensions
    {
        public static IServiceCollection AddDepartmentRepository(this IServiceCollection services , Action<DatabaseSettings> configureSettings)
        {
            return services.AddScoped<IDepartmentRepository>(serviceProvider =>
            {
                var settings = new DatabaseSettings(); 
                configureSettings(settings); 
                return ActivatorUtilities.CreateInstance<DepartmentRepository>(serviceProvider , settings); 
            });
        }
        public static IServiceCollection AddEmployeeRepository(this IServiceCollection services, Action<DatabaseSettings> configureSettings)
        {
            return services.AddScoped<IEmployeeRepository>(serviceProvider =>
            {
                var settings = new DatabaseSettings();
                configureSettings(settings);
                return ActivatorUtilities.CreateInstance<EmployeeRepository>(serviceProvider, settings);
            });
        }
        public static IServiceCollection AddProjectRepository(this IServiceCollection services, Action<DatabaseSettings> configureSettings)
        {
            return services.AddScoped<IProjectRepository>(serviceProvider =>
            {
                var settings = new DatabaseSettings();
                configureSettings(settings);
                return ActivatorUtilities.CreateInstance<ProjectRepository>(serviceProvider, settings);
            });
        }
    }
}

4. Add the repositories to the Dependency Injection Container with DatabaseSettings configuration.

5. Add three controllers  that will interact with azure cosmos db to the Controllers folder (see below).

DepartmentController

// Add services to the container.
builder.Services.AddControllers();
var configuration = builder.Configuration;
CosmosClientOptions cosmosClientOptions = new CosmosClientOptions()
{
    SerializerOptions = new() { IgnoreNullValues = true },
};
builder.Services.AddSingleton(s => new CosmosClient(configuration.GetValue<string>("AccountEndpoint"), configuration.GetValue<string>("AuthKey"), cosmosClientOptions));
builder.Services.AddDepartmentRepository(settings =>
{
    settings.DatabaseId = configuration.GetValue<string>("DatabaseId");
    settings.ContainerId = configuration.GetValue<string>("DepartmentContainer"); ;
});
builder.Services.AddEmployeeRepository(settings =>
{
    settings.DatabaseId = configuration.GetValue<string>("DatabaseId");
    settings.ContainerId = configuration.GetValue<string>("EmployeeContainer"); ;
});
builder.Services.AddProjectRepository(settings =>
{
    settings.DatabaseId = configuration.GetValue<string>("DatabaseId");
    settings.ContainerId = configuration.GetValue<string>("ProjectContainer"); ;
});
namespace CS.Staff.ApiApp.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class DepartmentController : ControllerBase
    {
        private readonly IDepartmentRepository departmentRepository;
        public DepartmentController(IDepartmentRepository departmentRepository)
        {
            this.departmentRepository = departmentRepository;
        }
        [HttpGet]
        public async Task<IEnumerable<Department>> GetDepartmentsAsync(string filter)
        {
            return await departmentRepository.GetItemsAsync(filter).ConfigureAwait(false); 
        }
        [HttpGet]
        [Route("{id}")]
        public async Task<Department> GetDepartmentById(string id, [FromQuery][Required] string partitionKey)
        {
            return await departmentRepository.FindItemAsync(id, partitionKey).ConfigureAwait(false);
        }
        [HttpPost]
        public async Task<Department> CreateDepartmentAsync([FromBody] Department department)
        {
            if (department == null || department.Etag != null)
            {
                return null;
            }
            return await departmentRepository.AddItemAsync(department, department.Location).ConfigureAwait(false);
        }
        [HttpPut]
        public async Task<Department> UpdateDepartmentAsync([FromBody] Department department)
        {
            return await departmentRepository.UpdateItemAsync(department, department.Id,department.Etag, department.Location ).ConfigureAwait(false);
        }
        [HttpDelete] 
        public async Task<bool> RemoveDepartmentAsync(string id, [FromQuery][Required] string partitionKey)
        {
            return await departmentRepository.RemoveItemAsync(id, partitionKey).ConfigureAwait(false);
        }
    }
}

EmployeeController

namespace CS.Staff.ApiApp.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class EmployeeController : ControllerBase
    {
        private readonly IEmployeeRepository employeeRepository;
        public EmployeeController(IEmployeeRepository employeeRepository)
        {
            this.employeeRepository = employeeRepository;
        }
        [HttpGet]
        public async Task<IEnumerable<Employee>> GetEmployeesAsync(string filter)
        {
            return await employeeRepository.GetItemsAsync(filter).ConfigureAwait(false);
        }
        [HttpGet]
        [Route("{id}")]
        public async Task<Employee> GetEmployeeById(string id, [FromQuery][Required] string partitionKey)
        {
            return await employeeRepository.FindItemAsync(id, partitionKey).ConfigureAwait(false);
        }
        [HttpPost]
        public async Task<Employee> CreateEmployeeAsync([FromBody] Employee employee)
        {
            return await employeeRepository.AddItemAsync(employee, employee.Email).ConfigureAwait(false);
        }
        [HttpPut]
        public async Task<Employee> UpdateEmployeeAsync([FromBody] Employee employee)
        {
            return await employeeRepository.UpdateItemAsync(employee, employee.Id, employee.Etag, employee.Email).ConfigureAwait(false);
        }
        [HttpDelete]
        public async Task<bool> RemoveEmployeeAsync(string id, [FromQuery][Required] string partitionKey)
        {
            return await employeeRepository.RemoveItemAsync(id, partitionKey).ConfigureAwait(false);
        }
    }
}

ProjectController

namespace CS.Staff.ApiApp.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class ProjectController : ControllerBase
    {
        private readonly IProjectRepository projectRepository;
        public ProjectController(IProjectRepository projectRepository)
        {
            this.projectRepository = projectRepository;
        }
        [HttpGet]
        public async Task<IEnumerable<Project>> GetProjectsAsync(string filter)
        {
            return await projectRepository.GetItemsAsync(filter).ConfigureAwait(false);
        }
        [HttpGet]
        [Route("{id}")]
        public async Task<Project> GetProjectById(string id, [FromQuery][Required] string partitionKey)
        {
            return await projectRepository.FindItemAsync(id, partitionKey).ConfigureAwait(false);
        }
        [HttpPost]
        public async Task<Project> CreateProjectAsync([FromBody] Project project)
        {
            return await projectRepository.AddItemAsync(project, project.Id).ConfigureAwait(false);
        }
        [HttpPut]
        public async Task<Project> UpdateProjectAsync([FromBody] Project project)
        {
            return await projectRepository.UpdateItemAsync(project, project.Id, project.Etag, project.Id).ConfigureAwait(false);
        }
        [HttpDelete]
        public async Task<bool> RemoveProjectAsync(string id, [FromQuery][Required] string partitionKey)
        {
            return await projectRepository.RemoveItemAsync(id, partitionKey).ConfigureAwait(false);
        }
    }
}

6. Run the application and test it.

Run the solution

6. Create a Department with related Employees and retrieve it from the azure cosmos db database.

Create entity
response cosmos db

The code for the demo can be found  Here


Conclusion

The repository pattern can improve a Cosmos DB application, but only when it preserves the database’s real contract. Require partition keys, choose point reads over queries, return continuation tokens instead of loading entire containers, use ETags for concurrency, and retain RU and diagnostic information. That produces an abstraction developers can reason about in production—not merely a generic CRUD wrapper.

References

Found this useful? Support more practical developer content.

Author

Practical .NET, Angular, Azure, Blazor, and AI engineering for real-world development.

Ads Blocker Image Powered by Code Help Pro

Ads Blocker Detected!!!

We have detected that you are using extensions to block ads. Please support us by disabling these ads blocker.

Powered By
Best Wordpress Adblock Detecting Plugin | CHP Adblock