Wiaoj.Ddd.EntityFrameworkCore 0.0.1-alpha.80

This is a prerelease version of Wiaoj.Ddd.EntityFrameworkCore.
dotnet add package Wiaoj.Ddd.EntityFrameworkCore --version 0.0.1-alpha.80
                    
NuGet\Install-Package Wiaoj.Ddd.EntityFrameworkCore -Version 0.0.1-alpha.80
                    
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="Wiaoj.Ddd.EntityFrameworkCore" Version="0.0.1-alpha.80" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Wiaoj.Ddd.EntityFrameworkCore" Version="0.0.1-alpha.80" />
                    
Directory.Packages.props
<PackageReference Include="Wiaoj.Ddd.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 Wiaoj.Ddd.EntityFrameworkCore --version 0.0.1-alpha.80
                    
#r "nuget: Wiaoj.Ddd.EntityFrameworkCore, 0.0.1-alpha.80"
                    
#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 Wiaoj.Ddd.EntityFrameworkCore@0.0.1-alpha.80
                    
#: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=Wiaoj.Ddd.EntityFrameworkCore&version=0.0.1-alpha.80&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Wiaoj.Ddd.EntityFrameworkCore&version=0.0.1-alpha.80&prerelease
                    
Install as a Cake Tool

Wiaoj.Ddd

Wiaoj.Ddd is a comprehensive, high-performance Domain-Driven Design (DDD) framework for .NET. It provides the essential building blocks for implementing complex business logic while handling cross-cutting concerns like Domain Events, Audit Logging, and the Transactional Outbox pattern seamlessly.

Built on top of the Wiaoj Ecosystem (Primitives, Serialization, Extensions), it ensures type safety, zero-allocation best practices, and modular architecture.

🌟 Key Features

  • 🧱 Core Building Blocks: Robust base classes for Aggregate<TId>, Entity<TId>, and ValueObject.
  • 📣 Domain Events System:
    • Pre-Commit Handlers: Run logic within the same transaction (e.g., validation, cascade updates).
    • Post-Commit Handlers: Run logic after the transaction commits (via Outbox).
  • 📦 Transactional Outbox Pattern:
    • Automatically captures Domain Events during SaveChanges.
    • Serializes events using Wiaoj.Serialization (System.Text.Json, MessagePack, etc.).
    • Background processor guarantees at-least-once delivery.
  • 🕵️ Audit Logging: Automatic tracking of CreatedAt, UpdatedAt, and DeletedAt (Soft Delete) via EF Core Interceptors.
  • 🔌 Pluggable Serialization: Decoupled from specific serialization libraries. Use System.Text.Json, MessagePack, or Bson via configuration.

📦 Installation

# Core Abstractions & Logic
dotnet add package Wiaoj.Ddd

# Entity Framework Core Integration (Outbox & Interceptors)
dotnet add package Wiaoj.Ddd.EntityFrameworkCore

🚀 Quick Start

1. Define Your Domain Model

Create your Aggregates and Domain Events using the provided base classes.

using Wiaoj.Ddd.Abstractions;
using Wiaoj.Ddd.Abstractions.DomainEvents;

// 1. Define a Domain Event
public sealed record UserRegisteredEvent(Guid UserId, string Email) : DomainEvent;

// 2. Define an Aggregate Root
public class User : Aggregate<UserId> // UserId is a strong typed Value Object
{
    public string Email { get; private set; }
    public string Name { get; private set; }

    // Enforce invariants in the constructor
    public User(UserId id, string email, string name) : base(id)
    {
        Email = email;
        Name = name;

        // Raise a domain event
        RaiseDomainEvent(new UserRegisteredEvent(id.Value, email));
    }

    public void UpdateName(string newName)
    {
        Name = newName;
        // CreatedAt, UpdatedAt are handled automatically by the AuditInterceptor
    }
}

2. Implement Event Handlers

Handle events either synchronously before commit or asynchronously after commit.

// Runs BEFORE the DB transaction commits.
// Good for: Validations, updating other aggregates in the same transaction.
public class UserValidationHandler : IPreDomainEventHandler<UserRegisteredEvent>
{
    public ValueTask Handle(UserRegisteredEvent @event, CancellationToken ct)
    {
        // Logic here...
        return ValueTask.CompletedTask;
    }
}

// Runs AFTER the DB transaction commits (via Outbox Processor).
// Good for: Sending emails, publishing to Message Bus (RabbitMQ/Kafka).
public class WelcomeEmailHandler : IPostDomainEventHandler<UserRegisteredEvent>
{
    public async ValueTask Handle(UserRegisteredEvent @event, CancellationToken ct)
    {
        await _emailService.SendWelcomeAsync(@event.Email);
    }
}

3. Configure Dependency Injection

Wire everything up in your Program.cs.

using Wiaoj.Serialization.DependencyInjection; // For UseSystemTextJson

var builder = WebApplication.CreateBuilder(args);

// Register DDD Services
builder.Services.AddDdd(ddd =>
{
    // Auto-scan assemblies for Event Handlers
    ddd.ScanAssemblies(ServiceLifetime.Scoped, typeof(Program).Assembly);
})
.AddEntityFrameworkCore<MyDbContext>(
    // 1. Configure Serialization (Mandatory for flexibility)
    configureSerializer: serializer => 
    {
        // Use System.Text.Json (or MessagePack/Bson) for Outbox payload
        serializer.UseSystemTextJson<DddEfCoreOutboxSerializerKey>(); 
    },
    // 2. Configure Outbox Options (Optional)
    configureOutbox: options =>
    {
        options.BatchSize = 50;
        options.PollingInterval = TimeSpan.FromSeconds(2);
    }
);

4. Setup DbContext

Apply the necessary configurations to your DbContext.

public class MyDbContext : DbContext, IOutboxDbContext
{
    public DbSet<User> Users { get; set; }

    public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // Creates the '_CorvusOutboxMessages' table
        modelBuilder.ApplyCorvusOutbox(new EfCoreOutboxConfiguration(null!)); 
        base.OnModelCreating(modelBuilder);
    }

}

Attach the interceptors when registering the DbContext. EF Core does not reliably auto-discover DI-registered interceptors for every mode (notably AddDbContextFactory and pooling), so call UseDddInterceptors(sp) inside the registration delegate. This works the same for AddDbContext, AddDbContextPool, and AddDbContextFactory:

services.AddDbContextFactory<MyDbContext>((sp, options) => options
    .UseNpgsql(connectionString)
    .UseDddInterceptors<MyDbContext>(sp)); // attaches only MyDbContext's interceptors

Only the interceptors belonging to the specified context are attached, so in a multi-context app each context opts in independently and no per-save context filtering is needed.


🏗️ Architecture & Concepts

The Transactional Outbox

When you call SaveChangesAsync():

  1. AuditInterceptor: Updates CreatedAt / UpdatedAt timestamps automatically.
  2. DomainEventDispatcherInterceptor:
    • Detects aggregates with pending events.
    • Executes IPreDomainEventHandlers immediately.
    • Serializes events and saves them to the OutboxMessage table within the same transaction.
  3. Commit: The Aggregate changes and the Outbox messages are committed atomically.
  4. Background Processor: The OutboxProcessor background service polls the table, deserializes the events, and executes IPostDomainEventHandlers.

Serialization flexibility

Unlike other libraries that force a specific JSON library, Wiaoj.Ddd leverages Wiaoj.Serialization. You can store your outbox payloads using:

  • System.Text.Json (Default recommendation)
  • MessagePack (For smaller payload size)
  • MongoDB.Bson
  • YamlDotNet

DbContext Registration Modes

The Outbox and Domain Event infrastructure (interceptors, dispatcher, background processor) is registered as stateless singletons and works identically across all EF Core DbContext registration modes — AddDbContext (scoped), AddDbContextPool (pooled), and AddDbContextFactory.

There is one constraint to be aware of:

  • Repositories and IUnitOfWork require scoped registration (AddDbContext). EfcoreRepository<TContext, ...> resolves its DbContext from DI, so it only works when the context is registered as a scoped service. Under AddDbContextFactory/pooled, the context is created by the factory and is not available in the container, so repositories cannot be constructed.
  • Pre-commit handlers should write through IUnitOfWork for cross-mode safety. A pre-commit handler that injects TContext (or a repository bound to it) directly will receive the wrong/no context under factory/pooled mode, so its changes would not join the active transaction. Injecting IUnitOfWork resolves the live context (the one being saved) via the ambient holder, so staged changes — including plain Add without an inner SaveChanges — commit atomically with the aggregate in every mode.

Rule of thumb: if you use repositories or the Unit of Work, register your DbContext with AddDbContext. If you only need the Outbox/event dispatching, any registration mode works.

⚠️ Known Limitations

  • Outbox Concurrency: The built-in OutboxProcessor in this package uses a simple polling mechanism intended for single-instance deployments. If you deploy multiple replicas (e.g., Kubernetes), you may encounter race conditions where the same message is processed twice.
    • Solution: For high-scale, distributed environments, please upgrade to Wiaoj.Corvus.Outbox, which implements SKIP LOCKED and distributed locking strategies.

📄 License

Licensed under the MIT License.

Product Compatible and additional computed target framework versions.
.NET 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
0.0.1-alpha.80 41 6/17/2026
0.0.1-alpha.79 38 6/17/2026
0.0.1-alpha.78 45 6/17/2026
0.0.1-alpha.77 39 6/17/2026
0.0.1-alpha.76 38 6/17/2026
0.0.1-alpha.75 75 6/11/2026
0.0.1-alpha.74 57 6/10/2026
0.0.1-alpha.73 49 6/5/2026
0.0.1-alpha.72 59 6/4/2026
0.0.1-alpha.71 51 6/2/2026
0.0.1-alpha.70 54 5/30/2026
0.0.1-alpha.69 53 5/15/2026
0.0.1-alpha.68 55 5/15/2026
0.0.1-alpha.67 52 5/14/2026
0.0.1-alpha.66 50 5/13/2026
0.0.1-alpha.65 50 5/12/2026
0.0.1-alpha.64 47 5/12/2026
0.0.1-alpha.63 47 5/12/2026
0.0.1-alpha.62 61 5/8/2026
0.0.1-alpha.61 48 5/6/2026
Loading failed