SDDev.Net.GenericRepository
10.0.0
dotnet add package SDDev.Net.GenericRepository --version 10.0.0
NuGet\Install-Package SDDev.Net.GenericRepository -Version 10.0.0
<PackageReference Include="SDDev.Net.GenericRepository" Version="10.0.0" />
<PackageVersion Include="SDDev.Net.GenericRepository" Version="10.0.0" />
<PackageReference Include="SDDev.Net.GenericRepository" />
paket add SDDev.Net.GenericRepository --version 10.0.0
#r "nuget: SDDev.Net.GenericRepository, 10.0.0"
#:package SDDev.Net.GenericRepository@10.0.0
#addin nuget:?package=SDDev.Net.GenericRepository&version=10.0.0
#tool nuget:?package=SDDev.Net.GenericRepository&version=10.0.0
SDDev.Net Generic Repository for Azure Cosmos DB
The SDDev.Net.GenericRepository package implements the repository pattern on top of Azure Cosmos DB. It allows you to model your data as Plain Old C# Objects (POCOs) while the library handles container access, partition routing, query construction, and cross-cutting features such as caching, indexing, and patch updates. The goal is to make the common 90% of Cosmos DB development frictionless while still giving you escape hatches (for example, direct access to the Container client) for the remaining scenarios.
Prerequisite: The repository targets .NET 9. Install the .NET 9 SDK before building the solution or referencing the package.
The repository project ships together with a contracts package that contains base entity abstractions and search helpers. This repository hosts both packages, a test suite, and an example application that demonstrate how to use the tooling end-to-end.
Key capabilities
- POCO-first data access – inherit from
BaseStorableEntityorBaseAuditableEntityand interact with your models directly. - Flexible querying – build strongly-typed queries with LINQ expressions or dynamic queries through
System.Linq.Dynamic.Core. - Pagination support – use
SearchModelto request page sizes, continuation tokens, offsets, and sorting information. - Logical and physical deletes – toggle between setting a TTL for soft deletes or forcing an immediate removal.
- Patch support – issue partial updates using
CosmosPatchOperationCollectionwithout replacing entire documents. - Caching decorator – wrap repositories with
CachedRepository<T>to reduce hot reads. - Hierarchical partition support – target containers with composite partition keys through
HierarchicalPartitionedRepository<T>. - Indexing helpers – integrate with Azure Cognitive Search using the indexing abstractions when needed.
Packages
Add the following packages to your application (both target .NET 9):
dotnet add package SDDev.Net.GenericRepository
dotnet add package SDDev.Net.GenericRepository.Contracts
SDDev.Net.GenericRepositoryalready references the contracts package. Adding the contracts package explicitly is helpful when you want to compile shared models in a separate project.
Configuration
- Bind Cosmos DB settings – add the configuration section to
appsettings.json:
"CosmosDb": {
"Uri": "https://<your-account>.documents.azure.com:443/",
"AuthKey": "<your-key>",
"DefaultDatabaseName": "AppDatabase",
"DeleteTTL": 3600,
"IncludeTotalResultsByDefault": true,
"PopulateIndexMetrics": false
}
- Register dependencies – configure the Cosmos client, repository options, and repositories in
Program.csor your DI setup:
builder.Services.Configure<CosmosDbConfiguration>(
builder.Configuration.GetSection("CosmosDb"));
builder.Services.AddSingleton(sp =>
{
var settings = sp.GetRequiredService<IOptions<CosmosDbConfiguration>>().Value;
var cosmosClientOptions = new CosmosClientOptions
{
AllowBulkExecution = settings.EnableBulkQuerying
};
return new CosmosClient(settings.Uri, settings.AuthKey, cosmosClientOptions);
});
builder.Services.AddScoped<IRepository<MyEntity>, GenericRepository<MyEntity>>();
// Optional decorators
builder.Services.Decorate<IRepository<MyEntity>, CachedRepository<MyEntity>>();
You can override the default database name, container name, or partition key by providing values to the repository constructor when registering the dependency.
Modeling entities
using System.Collections.Generic;
public class MyEntity : BaseAuditableEntity
{
public string CustomerId { get; set; }
public string DisplayName { get; set; }
public List<string> Tags { get; set; } = new();
public override string PartitionKey => CustomerId;
}
BaseStorableEntityprovidesId,IsActive,ItemType,PartitionKey, and a TTL field.BaseAuditableEntityextendsBaseStorableEntityand automatically managesCreatedDateTime/ModifiedDateTimemetadata.- Override
PartitionKeyto supply the value stored in Cosmos DB when your partition key differs from the type name.
Working with repositories
Below is an end-to-end example inside an application service. Every method is asynchronous and can be awaited from ASP.NET Core minimal APIs, controllers, or background services.
public class MyService
{
private readonly IRepository<MyEntity> _repository;
public MyService(IRepository<MyEntity> repository)
{
_repository = repository;
}
public async Task<Guid> CreateAsync(MyEntity entity)
{
return await _repository.Create(entity);
}
public async Task<MyEntity> GetAsync(Guid id, string partitionKey)
{
return await _repository.Get(id, partitionKey);
}
public async Task<IReadOnlyCollection<MyEntity>> SearchAsync(string customerId)
{
var search = new SearchModel
{
PageSize = 20,
PartitionKey = customerId,
SortByField = nameof(MyEntity.DisplayName),
SortAscending = true
};
var result = await _repository.Get(x => x.CustomerId == customerId, search);
return result.Results.ToList();
}
public async Task UpdateAsync(MyEntity entity)
{
await _repository.Update(entity);
}
public async Task<Guid> UpsertAsync(MyEntity entity)
{
return await _repository.Upsert(entity);
}
public async Task<int> CountAsync(string customerId)
{
return await _repository.Count(x => x.CustomerId == customerId, customerId);
}
public async Task DeleteAsync(Guid id, string partitionKey, bool force = false)
{
await _repository.Delete(id, partitionKey, force);
}
}
Continuation tokens and paging
SearchModel.ContinuationToken accepts a base64 encoded token returned from a previous query. Set the token on subsequent requests to fetch the next page. You can also use Offset for small paged queries; Cosmos DB recommends continuation tokens for production workloads.
Dynamic queries
If you need to build queries at runtime, call Get(string query, ISearchModel model) and pass a dynamic LINQ expression:
var search = new SearchModel { PartitionKey = customerId, PageSize = 50 };
var response = await _repository.Get("DisplayName.StartsWith(\"A\")", search);
Logical vs. physical delete
Delete(id, partitionKey, force: false)(the default) sets the entity TTL to the configuredDeleteTTL. Cosmos DB removes the document automatically after the interval, giving you a soft delete window.Delete(id, partitionKey, force: true)immediately removes the document. Use this when you are certain you no longer need the data.
Patch updates
Use CosmosPatchOperationCollection to build partial updates. Audit metadata is updated automatically when targeting auditable entities.
using SDDev.Net.GenericRepository.CosmosDB.Patch.Cosmos;
var operations = new CosmosPatchOperationCollection<MyEntity>();
operations.Set(x => x.DisplayName, "Contoso (updated)");
operations.Add(x => x.Tags, "priority");
await _repository.Patch(entityId, partitionKey: customerId, operations);
Cached repositories
Wrap repositories with CachedRepository<T> to store point reads in IDistributedCache implementations such as Redis:
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = builder.Configuration.GetConnectionString("Redis");
});
builder.Services.AddScoped<IRepository<MyEntity>, GenericRepository<MyEntity>>();
builder.Services.Decorate<IRepository<MyEntity>, CachedRepository<MyEntity>>();
- Cache entries default to a 60 second sliding window.
- Call
ICachedRepository<T>.EvictorDeleteto remove cache entries explicitly when executing cross-entity operations.
Hierarchical partition keys
For containers that use composite partition keys, use IHierarchicalPartitionRepository<T> / HierarchicalPartitionedRepository<T> and pass the ordered key list:
builder.Services.AddScoped<IHierarchicalPartitionRepository<MyEntity>>(sp =>
new HierarchicalPartitionedRepository<MyEntity>(
sp.GetRequiredService<CosmosClient>(),
sp.GetRequiredService<ILogger<HierarchicalPartitionedRepository<MyEntity>>>(),
sp.GetRequiredService<IOptions<CosmosDbConfiguration>>(),
new List<string> { "CustomerId", "Region" },
collectionName: "MyEntities"));
var entity = await hierarchicalRepo.Get(id, new List<string> { customerId, region });
The repository handles translating the key list into a PartitionKey compatible with the Cosmos SDK.
Indexing integration
The IndexedRepository<T, TIndex> adds Azure Cognitive Search support on top of the base repository. When you decorate a repository with indexing, patch and CRUD operations synchronize content with your search index. Consult the indexing tests for practical examples and the following tips when wiring up the decorator:
- One-time setup – call
Initialize(indexClientName, repository, options)(orSetRepository/SetIndexClientName) after construction so the decorator knows whichIRepository<T>instance and Azure client registrations to use. - Customize mapping – subscribe to the
AfterMappingorAfterMappingAsyncevents to enrich the index model with data that is not stored in Cosmos DB. - Manual index rebuilds – use
CreateOrUpdateIndex()to deploy your schema andUpdateIndex(...)overloads to repopulate the index. The overload that accepts anidnow supports an optionalpartitionKeyparameter; omit it when your entity uses the default key. - Parallel refresh –
UpdateIndex(IList<T> entities, int maxDegreeOfParallelism = 1)lets you batch updates efficiently. Increase the degree of parallelism when reindexing larger datasets. - Bring-your-own models – call
Create(entity, indexModel)orUpdate(entity, indexModel)if you want to control the mapping step entirely.
var indexedRepository = serviceProvider.GetRequiredService<IIndexedRepository<MyEntity, MyEntityIndex>>();
indexedRepository.Initialize("SearchClient", innerRepository, new IndexRepositoryOptions
{
IndexName = "my-entities",
RemoveOnLogicalDelete = true
});
indexedRepository.AfterMapping += (indexModel, entity) =>
{
indexModel.Region = ResolveRegion(entity.CustomerId);
};
await indexedRepository.UpdateIndex(id, partitionKey: null); // optional partition key parameter
Samples and reference material
- Example application:
SDDev.Net.GenericRepository.Example– bootstrap project that you can expand to prototype your own usage. - Integration tests:
SDDev.Net.GenericRepository.Tests– comprehensive test suite covering query composition, patch operations, hierarchical partitioning, caching, and more. Specific files worth reviewing include:GenericRepositoryTests.csfor CRUD, queries, deletes, and counts.CachedRepositoryTests.csfor cache behavior.PatchOperationCollectionTests.csfor patch examples.HierarchicalPartitionRepositoryTests.csfor composite partition keys.IndexedRepositoryTests.csfor search integration.
Feel free to copy these tests into your solution as living documentation—they demonstrate the majority of supported operations and edge cases.
Troubleshooting
- Cross-partition queries – the repository warns when a search spans multiple partitions. Provide
SearchModel.PartitionKeywhenever possible to avoid RU spikes. - Index metrics – set
CosmosDbConfiguration.PopulateIndexMetricstotruewhile tuning indexes. The repository logs Cosmos index metrics for the first page of results to aid diagnostics. - Bulk workloads – enable
CosmosDbConfiguration.EnableBulkQueryingto turn on the Cosmos SDK bulk executor, reducing throttling for high-volume operations.
Contributing
Issues and pull requests are welcome! Run the test suite from the repository root before submitting changes:
dotnet test SDDev.Net.GenericRepository.sln
Happy coding!
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- AutoMapper (>= 16.0.0)
- Azure.Search.Documents (>= 11.7.0)
- LINQKit.Core (>= 1.2.9)
- Microsoft.AspNetCore.Http.Abstractions (>= 2.3.0)
- Microsoft.AspNetCore.JsonPatch (>= 10.0.1)
- Microsoft.Azure.Cosmos (>= 3.56.0)
- Microsoft.Extensions.Azure (>= 1.13.1)
- Microsoft.Extensions.Caching.Abstractions (>= 10.0.1)
- Microsoft.Extensions.Configuration (>= 10.0.1)
- Microsoft.Extensions.DependencyInjection (>= 10.0.1)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.1)
- Microsoft.Extensions.Logging (>= 10.0.1)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.1)
- Newtonsoft.Json (>= 13.0.4)
- SDDev.Net.GenericRepository.Contracts (>= 10.0.0)
- System.Linq.Dynamic.Core (>= 1.7.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 10.0.0 | 493 | 1/2/2026 |
| 9.0.2 | 1,174 | 11/23/2025 |
| 9.0.1 | 1,051 | 11/14/2025 |
| 9.0.0 | 699 | 10/26/2025 |
| 8.0.3 | 13,685 | 10/23/2024 |
| 8.0.2 | 1,270 | 8/23/2024 |
| 8.0.1 | 201 | 8/23/2024 |
| 8.0.0 | 1,485 | 6/15/2024 |
| 7.0.3 | 397 | 6/5/2024 |
| 7.0.2 | 193 | 6/5/2024 |
| 7.0.1 | 4,691 | 3/21/2024 |
| 7.0.0 | 419 | 3/8/2024 |
| 5.3.4 | 271 | 2/25/2024 |
| 5.3.3 | 2,597 | 11/27/2023 |
| 5.3.2 | 7,049 | 8/29/2023 |
| 5.3.1 | 654 | 8/17/2023 |
| 5.3.0 | 1,286 | 7/24/2023 |
| 5.2.4 | 312 | 7/18/2023 |
| 5.2.3 | 255 | 7/18/2023 |
| 5.2.1 | 1,788 | 5/20/2023 |
| 5.2.0 | 289 | 5/19/2023 |
| 5.1.4 | 541 | 5/1/2023 |
| 5.1.3 | 1,049 | 4/19/2023 |
| 5.1.2 | 331 | 4/16/2023 |
| 5.1.1 | 299 | 4/16/2023 |
| 5.1.0 | 300 | 4/16/2023 |
| 5.0.2 | 1,032 | 3/30/2023 |
| 5.0.1 | 374 | 3/30/2023 |
| 5.0.0 | 2,833 | 1/11/2023 |
| 4.1.3 | 917 | 12/13/2022 |
| 4.1.2 | 966 | 12/11/2022 |
| 4.1.1 | 637 | 12/3/2022 |
| 4.1.0 | 474 | 12/2/2022 |
| 4.0.8 | 555 | 12/1/2022 |
| 4.0.7 | 556 | 11/11/2022 |
| 4.0.6 | 3,998 | 7/3/2022 |
| 4.0.5 | 645 | 6/19/2022 |
| 4.0.3 | 611 | 6/18/2022 |
| 4.0.1 | 603 | 6/11/2022 |
| 4.0.0 | 600 | 6/11/2022 |
| 3.0.0 | 2,018 | 2/26/2022 |
| 2.0.4 | 901 | 11/17/2021 |
| 2.0.2 | 998 | 7/18/2021 |
| 2.0.1 | 632 | 6/22/2021 |
| 2.0.0 | 533 | 6/17/2021 |
| 1.0.1 | 1,164 | 8/24/2018 |
| 1.0.0 | 1,534 | 5/18/2018 |