A .NET library for configuring containers in Azure Cosmos DB and providing an easy way to read and write document resources using System.Text.Json.
- Container configuration — declarative setup of Cosmos DB containers with automatic provisioning
- Read/Write abstractions —
ICosmosReader<T>andICosmosWriter<T>for simple CRUD operations - Bulk operations —
ICosmosBulkReader<T>andICosmosBulkWriter<T>for high-throughput batch operations - Change feed processing — built-in change feed processor support with partitioned data handling
- Paged queries — efficient pagination with continuation token support
- LINQ queries — query builder support using
IQueryable<T> - Cross-partition queries — read and query across partition boundaries
- Optimistic concurrency — ETag-based conflict detection on write operations
- Update pattern — read-modify-write with automatic retry on conflicts
- Delete by partition key — bulk delete all documents in a partition
- Multi-database support — connect to multiple Cosmos DB databases from a single application
- Unit testing fakes —
FakeCosmos<T>for testing without a real Cosmos DB instance - System.Text.Json — serialization using
System.Text.Jsonwith configurableJsonSerializerOptions
dotnet add package Atc.CosmosOnce the library is added to your project, you will have access to the following interfaces for reading and writing Cosmos document resources:
| Interface | Description |
|---|---|
ICosmosReader<T> |
Read Cosmos resources |
ICosmosWriter<T> |
Write Cosmos resources |
ICosmosBulkReader<T> |
Bulk read operations |
ICosmosBulkWriter<T> |
Bulk write operations |
A document resource is represented by a class deriving from the CosmosResource base class, or by implementing the ICosmosResource interface directly.
To configure where each resource will be stored in Cosmos, the ConfigureCosmos(builder) extension method is used on the IServiceCollection when setting up dependency injection.
The library uses the CosmosOptions class for configuring the connection to Cosmos:
| Name | Description |
|---|---|
AccountEndpoint |
URL to the Cosmos Account |
AccountKey |
Key for the Cosmos Account |
DatabaseName |
Name of the Cosmos database (will be provisioned by the library) |
DatabaseThroughput |
Throughput provisioned for the database in Request Units per second |
SerializerOptions |
JsonSerializerOptions used for System.Text.Json.JsonSerializer |
Credential |
TokenCredential for Azure AD authentication. When set, AccountKey is ignored |
There are 3 ways to provide the CosmosOptions to the library:
-
As an argument to the
ConfigureCosmos()extension method. -
As a
Func<IServiceProvider, CosmosOptions>factory method argument on theConfigureCosmos()extension method. -
As an
IOptions<CosmosOptions>instance configured using the Options framework and registered in dependency injection.This could be done by reading the
CosmosOptionsfrom configuration:services.Configure<CosmosOptions>( Configuration.GetSection(configurationSectionName));
Or by using a factory class implementing the
IConfigureOptions<CosmosOptions>interface:services.ConfigureOptions<ConfigureCosmosOptions>();
The latter is the recommended approach.
For each Cosmos resource you want to access using the readers and writers you will need to:
-
Create a class representing the Cosmos document resource.
The class should implement the abstract
CosmosResourcebase class, which requiresGetDocumentId()andGetPartitionKey()methods to be implemented.The class will be serialized using
System.Text.Json.JsonSerializer, soJsonPropertyNameAttributecan be used to control property names in the JSON document. -
Configure the container used for the Cosmos document resource.
This is done on the
ICosmosBuildermade available using theConfigureCosmos()extension onIServiceCollection:builder.Services.ConfigureCosmos(b => b.AddContainer<MyResource>(containerName));
-
Connect to multiple databases by scoping your container to a new
CosmosOptionsinstance:builder.Services.ConfigureCosmos( b => b.AddContainer<MyResource>(containerName) .ForDatabase(secondDbOptions) .AddContainer<MySecondResource>(containerName));
The first call to
AddContaineris scoped to the default options. The call toForDatabasereturns a new builder scoped to the provided options.
The library supports adding initializers for each container that can create the container and configure it with the correct keys and indexes.
-
Create an initializer by implementing the
ICosmosContainerInitializerinterface.Usually the implementation will call
CreateContainerIfNotExistsAsync()on the providedDatabaseobject with the desiredContainerProperties. -
Register the initializer on the
ICosmosBuilder:builder.Services.ConfigureCosmos(b => b.AddContainer<MyInitializer>(containerName));
-
Run the initialization using a hosted service:
builder.Services.ConfigureCosmos(b => b.UseHostedService());
Once the setup is in place, the readers and writers are registered with the Microsoft.Extensions.DependencyInjection container and can be obtained via constructor injection.
The bulk reader and writer optimize performance when executing many operations towards Cosmos. They work by creating all the tasks and then using Task.WhenAll() to await them, grouping operations by partition key and sending them in batches of 100.
When not operating with bulks, the normal readers are faster as there is no delay waiting for more work.
The library supports adding change feed processors for a container.
-
Create a processor by implementing the
IChangeFeedProcessorinterface. -
Register the change feed processor during initialization:
builder.Services.ConfigureCosmos(b => b .AddContainer<MyInitializer, MyResource>(containerName) .WithChangeFeedProcessor<MyChangeFeedProcessor>());
Or using the
ICosmosContainerBuilder<T>:builder.Services.ConfigureCosmos(b => b .AddContainer<MyInitializer>( containerName, c => c .AddResource<MyResource>() .WithChangeFeedProcessor<MyChangeFeedProcessor>()));
Note: The change feed processor relies on a HostedService, which means this feature is only available in hosted applications.
The ICosmosWriter<T>.DeletePartitionAsync() method allows you to delete all documents within a partition by partition key. This uses the Cosmos DB delete all items by partition key feature.
The library exposes low priority readers and writers:
| Interface | Description |
|---|---|
ILowPriorityCosmosReader<T> |
Read Cosmos resources with low priority |
ILowPriorityCosmosWriter<T> |
Write Cosmos resources with low priority |
ILowPriorityCosmosBulkReader<T> |
Bulk read with low priority |
ILowPriorityCosmosBulkWriter<T> |
Bulk write with low priority |
The "Priority Based Execution" feature needs to be enabled on the CosmosDB account, either in the Azure Portal under Settings > Features, or via Azure CLI:
az cosmosdb update --resource-group $ResourceGroup --name $AccountName --enable-priority-based-execution trueSee Microsoft Learn for more details.
The reader and writer interfaces can easily be mocked, but in some cases it is useful to have a fake implementation that mimics the behavior of read and write operations. The Atc.Cosmos.Testing namespace provides:
| Class | Description |
|---|---|
FakeCosmosReader<T> |
Fake ICosmosReader<T> / ICosmosBulkReader<T> |
FakeCosmosWriter<T> |
Fake ICosmosWriter<T> / ICosmosBulkWriter<T> |
FakeCosmos<T> |
Combined fake reader and writer with shared state |
Using Atc.Test, a test using the fakes could look like this:
[Theory, AutoNSubstituteData]
public async Task Should_Update_Cosmos_With_NewData(
[Frozen(Matching.ImplementedInterfaces)]
FakeCosmos<MyCosmosResource> cosmos,
MyCosmosService sut,
MyCosmosResource resource,
string newData)
{
cosmos.Documents.Add(resource);
await sut.UpdateAsync(resource.Id, newData);
resource
.Data
.Should()
.Be(newData);
}See the sample API for an example of how to configure the library with a minimal API, including container initialization, reading, and writing resources.
- .NET 8 SDK (or later)