DKNet

Publish queued events after a successful save

Part of DKNet.EfCore.Events. Connect entity event queues to one or more IEventPublisher implementations through the save hook.

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.AddEventPublisher<DemoDb, ConsolePublisher>();
using var provider = services.BuildServiceProvider();
using var scope = provider.CreateScope();
await using var db = scope.ServiceProvider.GetRequiredService<DemoDb>();
await db.Database.EnsureCreatedAsync();
var item = new Item();
item.Announce();
db.Add(item);
await db.SaveChangesAsync();

public sealed class DemoDb(DbContextOptions<DemoDb> options) : DbContext(options)
{
    public DbSet<Item> Items => Set<Item>();
}
public sealed class Item : DKNet.EfCore.Abstractions.Entities.Entity<int>
{
    public string Name { get; set; } = "Coffee";
    public void Announce() => AddEvent(new ItemCreated(Name));
}
public sealed record ItemCreated(string Name);
public sealed class ConsolePublisher : DKNet.EfCore.Abstractions.Events.DefaultEventPublisher
{
    public override Task PublishAsync(object value, CancellationToken cancellationToken = default)
    {
        Console.WriteLine(value); // ItemCreated { Name = Coffee }
        return Task.CompletedTask;
    }
}

EventContext collects entity event queues, EventHook sends the combined batch to each publisher, and it clears queues after attempting all publishers.

Only entities in the changed-entry snapshot participate in manual queue dispatch. Object events pass through; queued event types map from the live entity. EventContext de-duplicates each entity queue using object equality and stamps sourceType on manually queued IEventItem payloads.

⚙️ Configuration reference

Registration Lifetime / default Effect
AddEventPublisher<TDbContext, TPublisher>() Scoped publisher Add publisher and EventHook for this context.
AddDbContextWithHook<TDbContext>() Scoped context by default Install async-save interceptor.
MapsterMapper.IMapper host-supplied, first resolved Needed for queued event types and declared events.
ILogger<EventHook> optional Information per after-save pass and errors for publisher failures.

🧱 Where it fits

Abstractions defines the entity queues and publisher contracts. Hooks invokes EventHook around the context save, and the host publisher implementation owns transport or external side effects.

The package component map shows the host, contracts and consumers involved in this capability.

⚠️ Gotchas & limits