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 adiscriminatorfor 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
 
             
         
         
         
         
        