Dot.Conductor
3.0.1
dotnet add package Dot.Conductor --version 3.0.1
NuGet\Install-Package Dot.Conductor -Version 3.0.1
<PackageReference Include="Dot.Conductor" Version="3.0.1" />
<PackageVersion Include="Dot.Conductor" Version="3.0.1" />
<PackageReference Include="Dot.Conductor" />
paket add Dot.Conductor --version 3.0.1
#r "nuget: Dot.Conductor, 3.0.1"
#:package Dot.Conductor@3.0.1
#addin nuget:?package=Dot.Conductor&version=3.0.1
#tool nuget:?package=Dot.Conductor&version=3.0.1
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 pluggableITenantStrategyabstraction.🕰️ Temporal table helpers exposing
TemporalAll,TemporalAsOf,TemporalBetweenandTemporalFromToquery extensions.📊 Structured logging via
StructuredDbContextLoggerplus 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:
UseSqlServerandUseSqlitehelpers.UseConnectionString/UseConnectionStringFactoryfor late-bound secrets.UseProvider/UseProviderAsyncfor bespoke providers (e.g. PostgreSQL via Npgsql).UseDevelopmentto toggle EF Core sensitive logging, detailed errors and console logging.Configure/ConfigureAsyncfor additional builder tweaks (e.g. interceptors, provider features or DI-driven options).- Convenience helpers like
UseCommandTimeout,UseQueryTrackingBehavior,ConfigureWarnings,EnableSensitiveDataLogging,EnableDetailedErrorsandEnableThreadSafetyChecks.
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,GetAllIncludingandStreamAllAsyncfor streaming / eager-loaded access.FindAsync,FindIncludingAsync,FirstOrDefaultIncludingAsync,GetByIdsIncludingAsyncandGetDictionaryAsyncfor targeted lookups.- Change-tracking helpers such as
ReloadAsync,Detach,AddRangeAsync,UpdateRangeAsyncandDeleteRangeAsync. - 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 | 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
- Microsoft.AspNetCore.Http.Abstractions (>= 2.2.0)
- Microsoft.EntityFrameworkCore (>= 10.0.0)
- Microsoft.EntityFrameworkCore.Sqlite (>= 10.0.0)
- Microsoft.EntityFrameworkCore.SqlServer (>= 10.0.0)
- Microsoft.Extensions.Caching.Memory (>= 10.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.0)
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 |