Part of DKNet.EfCore.Hooks. Implement a hook interface or derive from HookAsync to inspect the shared snapshot around a save.
Use the console-project setup in the Quick Start, then replace Program.cs with this example.
using Microsoft.EntityFrameworkCore;
using DKNet.EfCore.Hooks;
using Microsoft.Extensions.DependencyInjection;
using var connection = new Microsoft.Data.Sqlite.SqliteConnection("Data Source=:memory:");
connection.Open();
var services = new Microsoft.Extensions.DependencyInjection.ServiceCollection();
services.AddLogging();
services.AddDbContextWithHook<DemoDb>(o => o.UseSqlite(connection));
services.AddHook<DemoDb, ReportHook>();
using var provider = services.BuildServiceProvider();
using var scope = provider.CreateScope();
await using var db = scope.ServiceProvider.GetRequiredService<DemoDb>();
await db.Database.EnsureCreatedAsync();
db.Add(new Item { Name = "Coffee" });
await db.SaveChangesAsync(); // before: 1, then after: 1
public sealed class DemoDb(DbContextOptions<DemoDb> options) : DbContext(options)
{
public DbSet<Item> Items => Set<Item>();
}
public sealed class Item
{
public int Id { get; set; }
public string Name { get; set; } = "";
}
public sealed class ReportHook : HookAsync
{
public override Task BeforeSaveAsync(DKNet.EfCore.Extensions.Snapshots.SnapshotContext snapshot,
CancellationToken cancellationToken = default)
{
Console.WriteLine($"before: {snapshot.Entities.Count}");
return Task.CompletedTask;
}
public override Task AfterSaveAsync(DKNet.EfCore.Extensions.Snapshots.SnapshotContext snapshot,
CancellationToken cancellationToken = default)
{
Console.WriteLine($"after: {snapshot.Entities.Count}");
return Task.CompletedTask;
}
}
IHookBaseAsync is the marker; choose IBeforeSaveHookAsync, IAfterSaveHookAsync or combined IHookAsync. HookAsync provides no-op virtual methods. Hooks are keyed scoped services resolved from the context application provider, including registrations for its base DbContext types.
| Registration | Lifetime / default | Effect |
|---|---|---|
AddDbContextWithHook<T>(builder, contextLifetime, optionLifetime) |
both Scoped | Register context and attach interceptor; overloads accept provider-aware or typed options callbacks. |
AddHook<TDbContext, THook>() |
keyed Scoped | Register hook once per context/hook pair and ensure runner services exist. |
UseHooks<TDbContext>(provider) |
explicit options call | Attach an already registered keyed runner when using plain AddDbContext. |
| Hook runner | keyed Singleton | One interceptor per registered context type. |
Manual setup must both register hooks and call UseHooks inside a provider-aware AddDbContext options callback. AddHook alone does not attach the interceptor.
Custom hooks share the same interceptor and snapshot with EventHook, EfCoreAuditHook and DataOwnerHook. See who fills CreatedBy/UpdatedBy when combining the audit and ownership hooks. Keep domain operations on entities and use these infrastructure hooks for work tied to persistence.
NotSupportedException unless hooks are disabled.