Working with Azure Cosmos DB usually means re-creating the same scaffolding: ids, partition keys, discriminators, DI wiring, RU logging, batch utilities… rinse, repeat.
Benday.CosmosDb packages those patterns so you can focus on your domain model and queries—not SDK boilerplate.
What you get
- Domain & repo base classes that standardize
id
, partition key,_etag
, and adiscriminator
for safe type filtering in shared containers. - Owned-item patterns for multi-tenant apps (partition by owner).
- DI helpers to register a properly configured
CosmosClient
(with options for gateway mode, bulk execution, DefaultAzureCredential, etc.). - RU logging + cross-partition warnings to keep queries efficient.
- Batch utilities for chunking big operations.
Install
dotnet add package Benday.CosmosDb
(NuGet package: Benday.CosmosDb — latest listed 4.8.0.)
Quick start: API + Web UI
Below is a typical setup that mirrors the SampleApp.Api/SampleApp.WebUi approach in the repo, with your library doing most of the heavy lifting.
1) Define a model (owned item)
using Benday.CosmosDb.DomainModels;
public class TodoItem : OwnedItemBase // PartitionKey == OwnerId
{
public string Title { get; set; } = "";
public bool IsDone { get; set; }
}
2) Create a repository (owner-aware)
using Benday.CosmosDb.Repositories;
using Microsoft.Azure.Cosmos;
using Microsoft.Extensions.Options;
public class TodoRepository : CosmosOwnedItemRepository<TodoItem>
{
public TodoRepository(
IOptions<CosmosRepositoryOptions<TodoItem>> options,
CosmosClient client) : base(options, client) { }
}
Why
OwnedItemBase
+CosmosOwnedItemRepository<T>
?
Your library bakes in the “owned-by-user/tenant” pattern so the partition key is the owner id and the repository/query helpers automatically use it—clean, predictable RU usage without hand-rolling partition logic.
3) Configure Cosmos in appsettings.json
{
"Cosmos": {
"Endpoint": "https://<your-account>.documents.azure.com:443/",
"AccountKey": "<secret>",
"DatabaseName": "AppDb",
"ContainerName": "AppContainer",
"PartitionKey": "/ownerId",
"CreateStructures": true,
"DatabaseThroughput": 400,
"UseGatewayMode": false,
"AllowBulkExecution": true,
"UseHierarchicalPartitionKey": false,
"UseDefaultAzureCredential": false
}
}
4) Wire up DI in Program.cs
(API or Web UI)
using Benday.CosmosDb.Utilities;
using Benday.CosmosDb.Repositories;
var builder = WebApplication.CreateBuilder(args);
// Bind config section to CosmosConfig
var cosmosSection = builder.Configuration.GetSection("Cosmos");
var cosmosConfig = new CosmosConfig(
accountKey: cosmosSection["AccountKey"]!,
endpoint: cosmosSection["Endpoint"]!,
databaseName: cosmosSection["DatabaseName"]!,
containerName: cosmosSection["ContainerName"]!,
partitionKey: cosmosSection["PartitionKey"]!,
createStructures: bool.Parse(cosmosSection["CreateStructures"] ?? "false"),
databaseThroughput: int.Parse(cosmosSection["DatabaseThroughput"] ?? "400"),
useGatewayMode: bool.Parse(cosmosSection["UseGatewayMode"] ?? "false"),
useHierarchicalPartitionKey: bool.Parse(cosmosSection["UseHierarchicalPartitionKey"] ?? "false"),
allowBulkExecution: bool.Parse(cosmosSection["AllowBulkExecution"] ?? "true"),
useDefaultAzureCredential: bool.Parse(cosmosSection["UseDefaultAzureCredential"] ?? "false")
);
// Registers CosmosClient as a singleton, using options from CosmosConfig
builder.Services.ConfigureCosmosClient(cosmosConfig);
// Register repository options for your model
builder.Services.Configure<CosmosRepositoryOptions<TodoItem>>(o =>
{
o.DatabaseName = cosmosConfig.DatabaseName;
o.ContainerName = cosmosConfig.ContainerName;
});
// Register your repo (and optionally a service layer)
builder.Services.AddScoped<TodoRepository>();
var app = builder.Build();
app.MapGet("/", () => "OK");
app.Run();
5) Use the repository in an API endpoint
app.MapPost("/api/todos", async (TodoRepository repo, TodoItem dto, HttpContext ctx) =>
{
// assume you’ve resolved the owner id from auth
var ownerId = ctx.User?.Identity?.Name ?? "demo";
dto.OwnerId = ownerId;
await repo.SaveAsync(dto);
return Results.Created($"/api/todos/{dto.Id}", dto);
});
app.MapGet("/api/todos", async (TodoRepository repo, HttpContext ctx) =>
{
var ownerId = ctx.User?.Identity?.Name ?? "demo";
var results = await repo.GetResults(q => q.Where(x => x.OwnerId == ownerId));
return Results.Ok(results);
});
Optional: Service layer for thinner controllers / pages
Prefer keeping controllers razor-thin? Use a service that composes the repo:
using Benday.CosmosDb.ServiceLayers;
public class TodoService : IOwnedItemService<TodoItem>
{
private readonly TodoRepository _repo;
public TodoService(TodoRepository repo) => _repo = repo;
public Task DeleteAsync(TodoItem item) => _repo.DeleteAsync(item.Id);
public Task<TodoItem?> GetByIdAsync(string id) => _repo.GetByIdAsync(id);
public Task SaveAsync(TodoItem item) => _repo.SaveAsync(item);
// add other list/query methods that encapsulate OwnerId, etc.
}
Register TodoService
and call it from API/Web UI—this mirrors the OwnedItemService pattern in your package.
Batch operations (import/migrations)
When you need to save or process lots of items, use the batch helpers:
using Benday.CosmosDb.Utilities;
var batches = BatchUtility.CreateArrayForBatch(items, startIndex: 0, batchSize: 100);
foreach (var batch in batches)
{
// save/process each chunk
foreach (var item in batch) { await repo.SaveAsync(item); }
}
This pairs nicely with the package’s bulk execution option and keeps memory/throughput predictable.
Notes on partition keys & credentials
- Flat partition keys are the default as of v4.0; if you need hierarchical PKs, enable them via config.
- You can opt into DefaultAzureCredential for managed identities (great for Azure hosting) via config/DI helpers.
Links
- NuGet: Benday.CosmosDb
- GitHub repo: benday-inc/Benday.CosmosDb