CSharpEssentials.EntityFrameworkCore 4.0.0

There is a newer version of this package available.
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
                    
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="CSharpEssentials.EntityFrameworkCore" Version="4.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CSharpEssentials.EntityFrameworkCore" Version="4.0.0" />
                    
Directory.Packages.props
<PackageReference Include="CSharpEssentials.EntityFrameworkCore" />
                    
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 CSharpEssentials.EntityFrameworkCore --version 4.0.0
                    
#r "nuget: CSharpEssentials.EntityFrameworkCore, 4.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 CSharpEssentials.EntityFrameworkCore@4.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=CSharpEssentials.EntityFrameworkCore&version=4.0.0
                    
Install as a Cake Addin
#tool nuget:?package=CSharpEssentials.EntityFrameworkCore&version=4.0.0
                    
Install as a Cake Tool

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, UpdatedBy and 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 UPDATE statement.
  • 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 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. 
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
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
Loading failed