DKNet

DKNet.EfCore.Events

Domain-event collection and publishing during successful async saves for .NET developers using entity event queues or RaisesEvent declarations.

✨ Why use it?

🚀 Quick Start

Use a .NET 10 console project. Install the package, then replace Program.cs with the example.

dotnet new console -f net10.0 -n EfCoreDemo
cd EfCoreDemo
dotnet add package DKNet.EfCore.Events
dotnet add package Microsoft.EntityFrameworkCore.Sqlite --version 10.0.12
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;
    }
}

Run with dotnet run.

🏗️ Architecture

Entity queues and declarations reach EventHook through Hooks, then EventContext and the optional mapper produce batches for publisher adapters.

EventHook runs as a scoped Hooks implementation and reads the shared Extensions snapshot. IEventPublisher implementations are scoped, while the first IMapper is used for mapping-based sources. The package supplies no durable transport, outbox store or background dispatcher.

🔄 How it works

HookRunnerInterceptor invokes EventHook before the write to capture declarations and after success to map, publish and clear event queues.

AddEventPublisher registers the publisher and EventHook; AddDbContextWithHook installs the required interceptor. Publisher failures are logged and swallowed individually, while mapping errors propagate. An outer transaction may still be uncommitted when the after-save phase runs.

🧩 Features