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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="SDDev.Net.GenericRepository" Version="10.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SDDev.Net.GenericRepository" Version="10.0.0" />
                    
Directory.Packages.props
<PackageReference Include="SDDev.Net.GenericRepository" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add SDDev.Net.GenericRepository --version 10.0.0
                    
#r "nuget: SDDev.Net.GenericRepository, 10.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package SDDev.Net.GenericRepository@10.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=SDDev.Net.GenericRepository&version=10.0.0
                    
Install as a Cake Addin
#tool nuget:?package=SDDev.Net.GenericRepository&version=10.0.0
                    
Install as a Cake Tool

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 BaseStorableEntity or BaseAuditableEntity and 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 SearchModel to 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 CosmosPatchOperationCollection without 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.GenericRepository already references the contracts package. Adding the contracts package explicitly is helpful when you want to compile shared models in a separate project.

Configuration

  1. 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
}
  1. Register dependencies – configure the Cosmos client, repository options, and repositories in Program.cs or 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;
}
  • BaseStorableEntity provides Id, IsActive, ItemType, PartitionKey, and a TTL field.
  • BaseAuditableEntity extends BaseStorableEntity and automatically manages CreatedDateTime/ModifiedDateTime metadata.
  • Override PartitionKey to 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 configured DeleteTTL. 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>.Evict or Delete to 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) (or SetRepository/SetIndexClientName) after construction so the decorator knows which IRepository<T> instance and Azure client registrations to use.
  • Customize mapping – subscribe to the AfterMapping or AfterMappingAsync events to enrich the index model with data that is not stored in Cosmos DB.
  • Manual index rebuilds – use CreateOrUpdateIndex() to deploy your schema and UpdateIndex(...) overloads to repopulate the index. The overload that accepts an id now supports an optional partitionKey parameter; omit it when your entity uses the default key.
  • Parallel refreshUpdateIndex(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) or Update(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

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.PartitionKey whenever possible to avoid RU spikes.
  • Index metrics – set CosmosDbConfiguration.PopulateIndexMetrics to true while tuning indexes. The repository logs Cosmos index metrics for the first page of results to aid diagnostics.
  • Bulk workloads – enable CosmosDbConfiguration.EnableBulkQuerying to 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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