CSharpEssentials.EntityFrameworkCore
4.0.0
See the version list below for details.
dotnet add package CSharpEssentials.EntityFrameworkCore --version 4.0.0
NuGet\Install-Package CSharpEssentials.EntityFrameworkCore -Version 4.0.0
<PackageReference Include="CSharpEssentials.EntityFrameworkCore" Version="4.0.0" />
<PackageVersion Include="CSharpEssentials.EntityFrameworkCore" Version="4.0.0" />
<PackageReference Include="CSharpEssentials.EntityFrameworkCore" />
paket add CSharpEssentials.EntityFrameworkCore --version 4.0.0
#r "nuget: CSharpEssentials.EntityFrameworkCore, 4.0.0"
#:package CSharpEssentials.EntityFrameworkCore@4.0.0
#addin nuget:?package=CSharpEssentials.EntityFrameworkCore&version=4.0.0
#tool nuget:?package=CSharpEssentials.EntityFrameworkCore&version=4.0.0
CSharpEssentials.EntityFrameworkCore
Extensions, interceptors, and utilities for Entity Framework Core. Audit trails, domain events, pagination, and CQRS support.
Features
- Audit Interceptor — Auto-populates
CreatedAt,CreatedBy,UpdatedAt,UpdatedByand handles soft deletes. - Domain Event Interceptor — Dispatches domain events with before/after save timing and outbox support.
- Slow Query Logging — Logs queries exceeding a configurable threshold.
- Pagination — Offset-based and cursor-based pagination on
IQueryable. - Batch Soft Delete — Soft-delete many rows with a single
UPDATEstatement. - BaseDbContext — Opt-in interceptor attachment from DI and domain event dispatch around
SaveChanges. - CQRS Support — Register separate write and read DbContexts.
- SQL Connection Factories — Abstractions for raw SQL/Dapper scenarios.
Installation
dotnet add package CSharpEssentials.EntityFrameworkCore
Usage
Audit Interceptor
services.AddAuditInterceptor(() => "system-user");
services.AddDbContext<MyDbContext>((sp, options) =>
options.UseNpgsql(connectionString)
.AddInterceptors(sp.GetRequiredService<AuditInterceptor>()));
Domain Event Interceptor
services.AddSingleton<IDomainEventPublisher, MyPublisher>();
services.AddSingleton<DomainEventInterceptor>();
// In your entity
public void UpdatePrice(decimal newPrice)
{
Price = newPrice;
Raise(new PriceChangedEvent(Id, newPrice));
}
Pagination
using CSharpEssentials.EntityFrameworkCore.Pagination;
// Offset-based
var response = await dbContext.Users.PaginateAsync(
new PaginationRequest { PageNumber = 1, PageSize = 10 }, ct);
// Offset-based with page number and size
var page = await dbContext.Users.OrderBy(u => u.Id).PaginateAsync(pageNumber: 2, pageSize: 20, cancellationToken: ct);
// Synchronous, also works on in-memory IQueryable
var memoryPage = users.AsQueryable().Paginate(pageNumber: 1, pageSize: 10);
// Cursor-based
var cursorResponse = await dbContext.Logs.PaginateAsync(
new CursorPaginationRequest<DateTimeOffset> { Limit = 20, Cursor = lastDate },
cursorSelector: x => x.CreatedAt);
Batch Soft Delete
// UPDATE ... SET IsDeleted = 1, DeletedAt = @now, DeletedBy = @user WHERE ... AND NOT IsDeleted
int affected = await dbContext.Orders
.Where(o => o.CustomerId == customerId)
.SoftDeleteAsync(deletedBy: userId, timeProvider, ct);
Works for any entity implementing ISoftDeletable (e.g. SoftDeletableEntityBase). Rows already soft-deleted keep their original DeletedAt/DeletedBy. Like all ExecuteUpdate calls, it bypasses the change tracker and SaveChanges interceptors and needs a relational provider.
BaseDbContext
public sealed class AppDbContext(DbContextOptions<AppDbContext> options, IServiceScopeFactory scopeFactory)
: BaseDbContext<AppDbContext>(options, scopeFactory)
{
// Attach interceptors registered in DI (AddAuditInterceptor, AddSlowQueryInterceptor, ...).
protected override DbContextInterceptors InterceptorsFromServices =>
DbContextInterceptors.Audit | DbContextInterceptors.SlowQuery;
// Dispatch domain events around SaveChanges/SaveChangesAsync.
protected override bool DispatchDomainEventsOnSaveChanges => true;
}
Both hooks are off by default. Interceptors that are not registered in DI, or are already on the options, are skipped. When dispatch is on, BeforeSave events go to IDomainEventPublisher before the save, and AfterSave events go to IDomainEventOutbox (if registered) or IDomainEventPublisher after the save succeeds. Override DispatchDomainEventsAsync to change this. If you override OnConfiguring, call base.OnConfiguring(optionsBuilder).
The two domain event paths differ in when AfterSave events are handled:
DispatchDomainEventsOnSaveChanges |
DomainEventInterceptor |
|
|---|---|---|
AfterSave events |
after the save completes | in SavingChanges, before the database write |
| Outbox write | not atomic with the save: the save is already committed when StoreAsync runs; inside an explicit transaction it runs before that transaction commits |
before the save, in the same SaveChanges call |
| Save fails | AfterSave events are put back on their entities, so a retry dispatches them |
events are already dispatched |
Use one of them, not both. When DispatchDomainEventsOnSaveChanges is true, DbContextInterceptors.DomainEvents in InterceptorsFromServices is ignored and the DomainEventInterceptor is not attached. The context's service scope (ServiceProvider) is disposed with the context.
CQRS Write/Read
services.AddCqrsDbContexts<WriteDbContext, ReadDbContext>(
configureWrite: (sp, options) => options.UseNpgsql(connectionString),
configureRead: (sp, options) => options.UseNpgsql(connectionString).UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));
Slow Query Logging
services.AddSlowQueryInterceptor(TimeSpan.FromMilliseconds(500));
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. 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
- CSharpEssentials.Core (>= 4.0.0)
- CSharpEssentials.Entity (>= 4.0.0)
- CSharpEssentials.Enums (>= 4.0.0)
- CSharpEssentials.Json (>= 4.0.0)
- CSharpEssentials.Maybe (>= 4.0.0)
- CSharpEssentials.Results (>= 4.0.0)
- EFCore.NamingConventions (>= 10.0.0 && < 11.0.0)
- Microsoft.EntityFrameworkCore (>= 10.0.1 && < 11.0.0)
- Microsoft.Extensions.Caching.Abstractions (>= 10.0.1)
- Microsoft.Extensions.Caching.Memory (>= 10.0.1)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.1)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.1)
-
net8.0
- CSharpEssentials.Core (>= 4.0.0)
- CSharpEssentials.Entity (>= 4.0.0)
- CSharpEssentials.Enums (>= 4.0.0)
- CSharpEssentials.Json (>= 4.0.0)
- CSharpEssentials.Maybe (>= 4.0.0)
- CSharpEssentials.Results (>= 4.0.0)
- EFCore.NamingConventions (>= 8.0.0 && < 9.0.0)
- Microsoft.Bcl.TimeProvider (>= 8.0.0)
- Microsoft.EntityFrameworkCore (>= 8.0.0 && < 9.0.0)
- Microsoft.Extensions.Caching.Abstractions (>= 9.0.4)
- Microsoft.Extensions.Caching.Memory (>= 9.0.4)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.4)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.4)
- System.Text.Json (>= 9.0.4)
-
net9.0
- CSharpEssentials.Core (>= 4.0.0)
- CSharpEssentials.Entity (>= 4.0.0)
- CSharpEssentials.Enums (>= 4.0.0)
- CSharpEssentials.Json (>= 4.0.0)
- CSharpEssentials.Maybe (>= 4.0.0)
- CSharpEssentials.Results (>= 4.0.0)
- EFCore.NamingConventions (>= 9.0.0 && < 10.0.0)
- Microsoft.EntityFrameworkCore (>= 9.0.4 && < 10.0.0)
- Microsoft.Extensions.Caching.Abstractions (>= 9.0.4)
- Microsoft.Extensions.Caching.Memory (>= 9.0.4)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.4)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.4)
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 |
|---|---|---|
| 6.5.2 | 67 | 10/10/2026 |
| 6.5.1 | 65 | 10/10/2026 |
| 6.5.0 | 73 | 10/9/2026 |
| 6.4.0 | 67 | 10/9/2026 |
| 6.3.0 | 61 | 10/8/2026 |
| 6.2.0 | 64 | 10/8/2026 |
| 6.1.0 | 67 | 10/7/2026 |
| 6.0.0 | 65 | 10/7/2026 |
| 5.2.1 | 77 | 10/7/2026 |
| 5.2.0 | 72 | 10/7/2026 |
| 5.1.0 | 76 | 10/7/2026 |
| 5.0.0 | 81 | 10/6/2026 |
| 4.1.0 | 91 | 10/6/2026 |
| 4.0.0 | 90 | 10/5/2026 |
| 3.2.3 | 170 | 5/31/2026 |
| 3.2.2 | 133 | 5/31/2026 |
| 3.2.1 | 125 | 5/31/2026 |
| 3.2.0 | 124 | 5/30/2026 |
| 3.1.0 | 134 | 5/27/2026 |
| 3.0.8 | 130 | 5/20/2026 |