Dot.Conductor
5.0.0
dotnet add package Dot.Conductor --version 5.0.0
NuGet\Install-Package Dot.Conductor -Version 5.0.0
<PackageReference Include="Dot.Conductor" Version="5.0.0" />
<PackageVersion Include="Dot.Conductor" Version="5.0.0" />
<PackageReference Include="Dot.Conductor" />
paket add Dot.Conductor --version 5.0.0
#r "nuget: Dot.Conductor, 5.0.0"
#:package Dot.Conductor@5.0.0
#addin nuget:?package=Dot.Conductor&version=5.0.0
#tool nuget:?package=Dot.Conductor&version=5.0.0
Dot.Conductor - Unit of Work & Repository Pattern for .NET 10
A robust, production-ready implementation of the Unit of Work and Repository patterns for Entity Framework Core, with advanced data patterns, resilience, and caching.
✨ Features
| Category | Features |
|---|---|
| Core Patterns | Unit of Work, Generic Repository, Multi-tenancy |
| Database Providers | SQL Server, PostgreSQL, MySQL, SQLite |
| Data Patterns | 🎯 Specification Pattern, 🗑️ Soft Delete, 📝 Audit Logging |
| Performance | 💾 Caching, ⚡ Bulk Operations, 🔄 Retry Policies |
| Advanced | 📊 Temporal Tables, 🔌 Query Interceptors, 📄 Paging |
📦 Installation
dotnet add package Dot.Conductor
🚀 Quick Start
Basic Setup (SQL Server)
// Program.cs
services.AddUnitOfWorkAndRepositories<MyDbContext>(
Configuration.GetConnectionString("DefaultConnection"),
isDevelopment: builder.Environment.IsDevelopment()
);
Using Repository
public class UserService
{
private readonly IUnitOfWork<MyDbContext> _unitOfWork;
public UserService(IUnitOfWork<MyDbContext> unitOfWork) => _unitOfWork = unitOfWork;
public async Task<User?> GetUserAsync(int id)
{
var repo = _unitOfWork.GetRepository<User>();
return await repo.GetByIdAsync(id);
}
public async Task CreateUserAsync(User user)
{
var repo = _unitOfWork.GetRepository<User>();
await repo.AddAsync(user);
await _unitOfWork.CommitAsync();
}
}
🗄️ Multi-Database Provider Support
// PostgreSQL
services.AddPostgreSqlUnitOfWork<MyDbContext>(
"Host=localhost;Database=mydb;Username=user;Password=pass"
);
// MySQL
services.AddMySqlUnitOfWork<MyDbContext>(
"Server=localhost;Database=mydb;User=user;Password=pass",
serverVersion: new MySqlServerVersion(new Version(8, 0, 36))
);
// SQLite
services.AddSqliteUnitOfWork<MyDbContext>("Data Source=mydb.sqlite");
// Generic configuration
services.AddUnitOfWorkWithProvider<MyDbContext>(options =>
{
options.Provider = DatabaseProvider.PostgreSql;
options.ConnectionString = "your-connection-string";
});
🎯 Specification Pattern
Define reusable query specifications:
public class ActiveUsersSpec : Specification<User>
{
public ActiveUsersSpec()
{
AddCriteria(u => u.IsActive);
AddCriteria(u => !u.IsDeleted);
AddInclude(u => u.Orders);
ApplyOrderBy(u => u.LastName);
}
}
public class UsersByAgeSpec : Specification<User>
{
public UsersByAgeSpec(int minAge, int maxAge)
{
AddCriteria(u => u.Age >= minAge && u.Age <= maxAge);
ApplyPaging(skip: 0, take: 10);
ApplyAsNoTracking();
}
}
// Usage
var repo = _unitOfWork.GetRepository<User>();
var activeUsers = await repo.FindAsync(new ActiveUsersSpec());
var user = await repo.FirstOrDefaultAsync(new UsersByAgeSpec(18, 30));
var count = await repo.CountAsync(new ActiveUsersSpec());
🗑️ Soft Delete
Implement soft delete for entities:
// Entity
public class Order : ISoftDeletable
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsDeleted { get; set; }
public DateTime? DeletedAt { get; set; }
public string? DeletedBy { get; set; }
}
// Configure in DbContext
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplySoftDeleteFilters(); // Auto-filters all ISoftDeletable
}
// Usage
repo.SoftDelete(order); // Sets IsDeleted = true
repo.Restore(order); // Restores the entity
// Query including deleted
var allOrders = context.Orders.IncludeDeleted().ToList();
📝 Audit Logging
Automatically track who created/modified entities:
// Entity
public class Product : IAuditable
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime? ModifiedAt { get; set; }
public string? CreatedBy { get; set; }
public string? ModifiedBy { get; set; }
}
// Setup
services.AddDotConductorAuditing<MyUserContext>();
// Implement IUserContext
public class MyUserContext : IUserContext
{
private readonly IHttpContextAccessor _httpContext;
public string? GetCurrentUserId() =>
_httpContext.HttpContext?.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
public string? GetCurrentUserName() =>
_httpContext.HttpContext?.User.Identity?.Name;
}
// Register interceptor in DbContext
services.AddDbContext<MyDbContext>((sp, options) =>
{
options.AddInterceptors(sp.GetRequiredService<AuditSaveChangesInterceptor>());
});
⚡ Bulk Operations
High-performance bulk operations using EF Core 8+ ExecuteUpdate/ExecuteDelete:
var repo = _unitOfWork.GetRepository<Product>();
// Bulk insert
await repo.BulkInsertAsync(products);
// Bulk update - deactivate products
var updated = await repo.BulkUpdateAsync(
p => p.Category == "Discontinued",
setter => setter
.SetProperty(p => p.IsActive, false)
.SetProperty(p => p.ModifiedAt, DateTime.UtcNow)
);
// Bulk delete
var deleted = await repo.BulkDeleteAsync(p => p.ExpiryDate < DateTime.Today);
🔄 Resilience (Retry & Circuit Breaker)
Built-in resilience for transient failures:
services.AddDotConductorResilience(options =>
{
options.EnableRetry = true;
options.MaxRetryAttempts = 3;
options.RetryBaseDelay = TimeSpan.FromMilliseconds(200);
options.EnableCircuitBreaker = true;
options.CircuitBreakerFailureThreshold = 5;
options.CircuitBreakerDuration = TimeSpan.FromSeconds(30);
options.Timeout = TimeSpan.FromSeconds(10);
});
// Use in your code
var pipeline = serviceProvider.GetRequiredService<ResiliencePipeline>();
var result = await pipeline.ExecuteAsync(async ct =>
{
return await repo.GetByIdAsync(id, ct);
});
💾 Caching
Repository caching decorator:
services.AddDotConductorCaching(options =>
{
options.DefaultExpiration = TimeSpan.FromMinutes(5);
options.UseSlidingExpiration = true;
options.CacheGetById = true;
});
// Use CachedRepository decorator
var cachedRepo = new CachedRepository<Product>(
innerRepository, memoryCache, cachingOptions);
// First call hits database, subsequent calls hit cache
var product = await cachedRepo.GetByIdAsync(1);
🔌 Query Interceptors
Log and monitor queries:
// Add logging interceptor
services.AddQueryLogging(logParameters: true);
// Add slow query detection
services.AddSlowQueryDetection(slowQueryThreshold: TimeSpan.FromSeconds(1));
// Register in DbContext
services.AddDbContext<MyDbContext>((sp, options) =>
{
options.AddInterceptors(
sp.GetRequiredService<LoggingInterceptor>(),
sp.GetRequiredService<PerformanceInterceptor>()
);
});
📊 Temporal Tables (SQL Server)
Query historical data:
// Configure in DbContext
modelBuilder.Entity<Order>(entity =>
{
entity.ToTable("Orders", b => b.IsTemporal());
});
// Query historical data
var repo = _unitOfWork.GetRepository<Order>();
var history = repo.TemporalAll().Where(o => o.Id == orderId).ToList();
var orderAtTime = repo.TemporalAsOf(DateTime.UtcNow.AddDays(-7)).FirstOrDefault(o => o.Id == orderId);
💼 Transaction Management
await _unitOfWork.BeginTransactionAsync();
try
{
var accountRepo = _unitOfWork.GetRepository<Account>();
// ... perform operations ...
await _unitOfWork.CommitTransactionAsync();
}
catch
{
await _unitOfWork.RollbackTransactionAsync();
throw;
}
📄 Paged Results
var pagedResult = await repo.GetPagedDataAsync(
pageNumber: 1,
pageSize: 20,
orderBy: p => p.CreatedAt,
ascending: false
);
// PagedResult<T> contains:
// - Results: IEnumerable<T>
// - TotalCount: int
// - PageSize: int
// - CurrentPage: int
// - TotalPages: int (computed)
📚 API Reference
IRepository<TEntity>
| Method | Description |
|---|---|
GetAll(asNoTracking) |
Get all entities |
GetByIdAsync(id) |
Get by ID (int or Guid) |
FindAsync(predicate) |
Find matching entities |
FindAsync(specification) |
Find using specification |
FirstOrDefaultAsync(...) |
Get first match |
CountAsync(...) |
Count entities |
AnyAsync(...) |
Check existence |
AddAsync(entity) |
Add entity |
Update(entity) |
Update entity |
Delete(entity) |
Delete entity |
BulkInsertAsync(...) |
Bulk insert |
BulkUpdateAsync(...) |
Bulk update |
BulkDeleteAsync(...) |
Bulk delete |
SoftDelete(entity) |
Soft delete |
Restore(entity) |
Restore soft-deleted |
IUnitOfWork<TContext>
| Method | Description |
|---|---|
GetRepository<T>() |
Get repository instance |
CommitAsync() |
Save all changes |
BeginTransactionAsync() |
Start transaction |
CommitTransactionAsync() |
Commit transaction |
RollbackTransactionAsync() |
Rollback transaction |
ExecuteSqlRawAsync(sql) |
Execute raw SQL |
🔄 Migration from v5.0 to v5.1
New Features
- Specification Pattern (
ISpecification<T>,Specification<T>) - Soft Delete (
ISoftDeletable,SoftDeleteExtensions) - Audit Logging (
IAuditable,AuditSaveChangesInterceptor) - Bulk Operations (
BulkInsertAsync,BulkUpdateAsync,BulkDeleteAsync) - Resilience (
ResiliencePipeline,ResilienceOptions) - Caching (
CachedRepository<T>,CachingOptions) - Query Interceptors (
LoggingInterceptor,PerformanceInterceptor)
Breaking Changes
BulkUpdateAsyncuses EF Core 10'sAction<UpdateSettersBuilder<T>>signature
📄 License
MIT License - Copyright (c) 2024-2026 Nathan WILCKÉ
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
| 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.EntityFrameworkCore (>= 10.0.0)
- Microsoft.EntityFrameworkCore.Relational (>= 10.0.0)
- Microsoft.EntityFrameworkCore.Sqlite (>= 10.0.0)
- Microsoft.EntityFrameworkCore.SqlServer (>= 10.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.0)
- Npgsql.EntityFrameworkCore.PostgreSQL (>= 10.0.0)
- Pomelo.EntityFrameworkCore.MySql (>= 9.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 |
|---|---|---|
| 5.0.0 | 98 | 1/28/2026 |
| 3.0.1 | 434 | 12/8/2025 |
| 3.0.0 | 198 | 11/26/2025 |
| 2.0.1 | 181 | 8/15/2025 |
| 1.5.0 | 370 | 11/21/2024 |
| 1.4.0 | 1,470 | 10/17/2024 |
| 1.3.0 | 1,307 | 6/23/2024 |
| 1.2.23 | 461 | 3/11/2024 |
| 1.2.22 | 465 | 12/29/2023 |
| 1.2.21 | 237 | 12/29/2023 |
| 1.2.20 | 243 | 12/28/2023 |
| 1.2.19 | 252 | 12/19/2023 |
| 1.2.18 | 214 | 12/19/2023 |
| 1.2.17 | 201 | 12/19/2023 |
| 1.2.16 | 329 | 11/15/2023 |
| 1.2.15 | 238 | 11/5/2023 |
| 1.2.14 | 216 | 11/4/2023 |
| 1.2.13 | 194 | 11/4/2023 |
| 1.2.12 | 190 | 11/4/2023 |
| 1.2.11 | 199 | 11/4/2023 |