Dot.Conductor 3.0.1

dotnet add package Dot.Conductor --version 3.0.1
                    
NuGet\Install-Package Dot.Conductor -Version 3.0.1
                    
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="Dot.Conductor" Version="3.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Dot.Conductor" Version="3.0.1" />
                    
Directory.Packages.props
<PackageReference Include="Dot.Conductor" />
                    
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 Dot.Conductor --version 3.0.1
                    
#r "nuget: Dot.Conductor, 3.0.1"
                    
#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 Dot.Conductor@3.0.1
                    
#: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=Dot.Conductor&version=3.0.1
                    
Install as a Cake Addin
#tool nuget:?package=Dot.Conductor&version=3.0.1
                    
Install as a Cake Tool

Dot.Conductor

Dot.Conductor is a batteries-included implementation of the Unit of Work and Repository patterns for Entity Framework Core. It focuses on multi-tenant workloads, transactional resilience, temporal queries and observability while remaining provider-agnostic.

Table of Contents

Features

  • Unit of Work orchestration with retry-aware commits, transaction helpers and lifecycle observers.

  • 📦 Generic repositories offering eager loading helpers, paging utilities, dictionary & streaming helpers and caching primitives backed by IMemoryCache.

  • 🏢 First-class multi-tenancy via AddMultiTenantUnitOfWorkAndRepositories<TContext> and a pluggable ITenantStrategy abstraction.

  • 🕰️ Temporal table helpers exposing TemporalAll, TemporalAsOf, TemporalBetween and TemporalFromTo query extensions.

  • 📊 Structured logging via StructuredDbContextLogger plus observer callbacks for telemetry/auditing hooks.

  • 🧪 Comprehensive test suite covering repositories, temporal helpers, transactional execution and stored procedures (see Dot.Conductor.Tests).

Installation

Install the package from NuGet:

dotnet add package Dot.Conductor

Configuring dependency injection

All registration helpers live in Dot.Conductor.Extensions and assume you are working inside an ASP.NET Core (or generic host) application with IServiceCollection access.

Fluent options builder

The fluent options builder provides a single entry point for configuring providers, connection strings and development mode:

builder.Services.AddUnitOfWorkAndRepositories<MyDbContext>(options =>
{
    options
        .UseSqlServer(builder.Configuration.GetConnectionString("Default"), sql =>
        {
            sql.MigrationsAssembly("MyApp.Migrations");
        })
        .UseDevelopment(builder.Environment.IsDevelopment());
});

Capabilities include:

  • UseSqlServer and UseSqlite helpers.
  • UseConnectionString / UseConnectionStringFactory for late-bound secrets.
  • UseProvider / UseProviderAsync for bespoke providers (e.g. PostgreSQL via Npgsql).
  • UseDevelopment to toggle EF Core sensitive logging, detailed errors and console logging.
  • Configure / ConfigureAsync for additional builder tweaks (e.g. interceptors, provider features or DI-driven options).
  • Convenience helpers like UseCommandTimeout, UseQueryTrackingBehavior, ConfigureWarnings, EnableSensitiveDataLogging, EnableDetailedErrors and EnableThreadSafetyChecks.

Direct registration overloads

Prefer explicit connection strings? Use the overloads that accept strings or delegates:

builder.Services.AddUnitOfWorkAndRepositories<MyDbContext>(
    connectionString: builder.Configuration.GetConnectionString("Default"),
    sqlOptionsAction: sql => sql.MigrationsAssembly("MyApp.Migrations"),
    isDevelopment: builder.Environment.IsDevelopment());

Each overload optionally accepts a configureOptions delegate for last-minute tweaks to the DbContextOptionsBuilder.

Multi-tenant setup

For multi-tenant systems, register the multi-tenant helper and implement ITenantStrategy:

builder.Services.AddMultiTenantUnitOfWorkAndRepositories<TenantDbContext>(
    sqlOptionsAction: sql => sql.MigrationsAssembly("MyApp.Migrations"),
    isDevelopment: builder.Environment.IsDevelopment());

builder.Services.AddScoped<ITenantStrategy, HttpHeaderTenantStrategy>();

ITenantStrategy resolves tenant identifiers and connection strings per request. The registration wires up caching, repositories and the unit of work scoped per tenant.

UnitOfWorkFactory helpers

Need a unit of work outside the DI pipeline? Resolve UnitOfWorkFactory<TContext> and choose the overload that fits:

var factory = serviceProvider.GetRequiredService<UnitOfWorkFactory<MyDbContext>>();

// Build options synchronously
using var uow = factory.CreateUnitOfWork(builder =>
{
    builder.UseNpgsql(npgsqlConnectionString);
    builder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
});

// Or asynchronously with access to scoped services
await using var asyncUow = await factory.CreateUnitOfWorkAsync(async (sp, builder) =>
{
    var secrets = sp.GetRequiredService<ISecretProvider>();
    builder.UseSqlServer(await secrets.ResolveAsync("primary"));
}, isDevelopment: true);

// Need to hydrate from pre-built options?
var options = new DbContextOptionsBuilder<MyDbContext>()
    .UseSqlite("DataSource=:memory:")
    .Options;

using var optionsUow = factory.CreateUnitOfWork(options);

Each overload automatically wires registered IDbContextLogger interceptors and can mirror the development toggles (sensitive logging, detailed errors and console logging) used by the DI helpers when isDevelopment is true.

Custom provider configuration

Need full control? Supply a delegate that builds the options manually—synchronous or asynchronous:

builder.Services.AddUnitOfWorkAndRepositories<MyDbContext>(options =>
{
    options.UseProvider(async (sp, builder) =>
    {
        var secrets = sp.GetRequiredService<ISecretProvider>();
        var connection = await secrets.GetConnectionAsync("primary");
        builder.UseNpgsql(connection); // or any other provider
    });
});

The configureOptions parameter exposed by each overload receives the service provider, connection string (when available) and DbContextOptionsBuilder, letting you override provider options on a per-request basis.

Working with the Unit of Work

Inject IUnitOfWork<TContext> anywhere in your application to resolve repositories, manage transactions and coordinate commits.

public sealed class UsersController(IUnitOfWork<MyDbContext> unitOfWork)
{
    [HttpGet("{id:int}")]
    public async Task<IActionResult> GetUser(int id)
    {
        var user = await unitOfWork.GetRepository<User>().GetByIdIncludingAsync(
            id,
            asNoTracking: true,
            u => u.Roles,
            u => u.Claims);

        return user is null ? NotFound() : Ok(user);
    }
}

Repository helpers

The generic repository exposes a rich set of helpers out of the box:

  • GetAll, GetAllIncluding and StreamAllAsync for streaming / eager-loaded access.
  • FindAsync, FindIncludingAsync, FirstOrDefaultIncludingAsync, GetByIdsIncludingAsync and GetDictionaryAsync for targeted lookups.
  • Change-tracking helpers such as ReloadAsync, Detach, AddRangeAsync, UpdateRangeAsync and DeleteRangeAsync.
  • Existence helpers (ExistsAsync, AnyAsync, CountAsync) and predicate-based retrieval.

Caching utilities

Repositories optionally take advantage of IMemoryCache:

var repository = unitOfWork.GetRepository<Product>();
var product = await repository.GetByIdCachedAsync(productId, TimeSpan.FromMinutes(5));
var cached = await repository.GetCachedAsync(productId, () => repository.GetByIdAsync(productId), TimeSpan.FromMinutes(1));
repository.RemoveCached(productId);

Temporal querying

When your entities map to temporal tables, leverage built-in helpers:

var history = await unitOfWork.GetRepository<User>()
    .TemporalBetween(request.From, request.To)
    .Where(user => user.Id == request.UserId)
    .ToListAsync();

Paging and projections

Return paged results with optional filtering and projections:

var pagedUsers = await unitOfWork.GetRepository<User>().GetPagedDataAsync(
    pageNumber: request.Page,
    pageSize: request.PageSize,
    predicate: user => user.IsActive,
    orderBy: user => user.LastName,
    selector: user => new UserSummary(user.Id, user.FirstName, user.LastName));

GetPagedDataAsync overloads return PagedResult<T> objects containing metadata (TotalCount, PageSize, CurrentPage).

Transactions and observers

UnitOfWork<TContext> wraps resilient transactions and exposes observer hooks:

await unitOfWork.ExecuteInTransactionAsync(async (uow, ct) =>
{
    var orders = uow.GetRepository<Order>();
    await orders.AddAsync(newOrder);
});

Implement IUnitOfWorkObserver<TContext> (or inherit from UnitOfWorkObserverBase<TContext>) to receive callbacks when transactions begin, commits run or rollbacks occur.

Stored procedures

Expose stored procedures through simple POCO classes:

var procedures = unitOfWork.GetStoredProcedures<AccountProcedures>();
await procedures.ArchiveInactiveAccountsAsync();

GetStoredProcedures<T> resolves the class from DI, enabling constructor injection for database abstractions.

Using UnitOfWork outside a scope

The IServiceScopeFactory extensions simplify manual usage:

var success = await scopeFactory.UseUnitOfWork<MyDbContext, bool>("TenantConnection", async unitOfWork =>
{
    var repository = unitOfWork.GetRepository<User>();
    await repository.AddAsync(new User { Name = "Jane" });
    await unitOfWork.CommitAsync();
    return true;
});

Overloads exist for fire-and-forget execution when you do not need a return value.

Logging & observability

Implement or register an IDbContextLogger to observe EF Core command execution. The package ships with StructuredDbContextLogger, offering:

  • EventId-rich log entries.
  • Slow command detection (configurable SlowCommandThreshold).
  • Parameter capture with optional redaction of values.
builder.Services.AddSingleton<IDbContextLogger>(sp =>
{
    var logger = sp.GetRequiredService<ILoggerFactory>().CreateLogger("EfCore.Commands");

    return new StructuredDbContextLogger(
        logger,
        new DbContextLoggerOptions
        {
            IncludeParameters = true,
            IncludeParameterValues = builder.Environment.IsDevelopment(),
            SlowCommandThreshold = TimeSpan.FromMilliseconds(250)
        });
});

You can also attach the logger inline via optionsBuilder.AddDbContextLogger(loggerFactory.CreateLogger("EfCore.Commands"));.

Observers registered against the unit of work provide additional hooks for auditing, metrics and diagnostics.

Testing

The Dot.Conductor.Tests project contains xUnit-based regression tests covering repositories, paging, temporal queries, transaction observers and DI registration. Run the suite with:

dotnet test

Contributing

Issues and pull requests are welcome. Please include relevant tests when proposing changes, especially around transactional helpers, temporal queries and multi-tenant scenarios.

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
3.0.1 426 12/8/2025
3.0.0 185 11/26/2025
2.0.1 170 8/15/2025
1.5.0 358 11/21/2024
1.4.0 1,405 10/17/2024
1.3.0 1,286 6/23/2024
1.2.23 448 3/11/2024
1.2.22 449 12/29/2023
1.2.21 219 12/29/2023
1.2.20 228 12/28/2023
1.2.19 236 12/19/2023
1.2.18 200 12/19/2023
1.2.17 190 12/19/2023
1.2.16 322 11/15/2023
1.2.15 227 11/5/2023
1.2.14 207 11/4/2023
1.2.13 187 11/4/2023
1.2.12 181 11/4/2023
1.2.11 192 11/4/2023
1.2.10 184 11/4/2023
1.2.9 189 11/4/2023
1.2.8 190 11/4/2023
1.2.7 197 11/2/2023
1.2.6 196 11/2/2023
1.2.5 194 11/2/2023
1.2.4 198 11/2/2023
1.2.3 202 11/1/2023
1.2.2 194 11/1/2023
1.2.1 207 10/27/2023
1.2.0 210 10/27/2023
1.1.1 199 10/25/2023
1.1.0 216 10/19/2023