DKNet.EfCore.Events 9.0.26

dotnet add package DKNet.EfCore.Events --version 9.0.26
                    
NuGet\Install-Package DKNet.EfCore.Events -Version 9.0.26
                    
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="DKNet.EfCore.Events" Version="9.0.26" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DKNet.EfCore.Events" Version="9.0.26" />
                    
Directory.Packages.props
<PackageReference Include="DKNet.EfCore.Events" />
                    
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 DKNet.EfCore.Events --version 9.0.26
                    
#r "nuget: DKNet.EfCore.Events, 9.0.26"
                    
#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 DKNet.EfCore.Events@9.0.26
                    
#: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=DKNet.EfCore.Events&version=9.0.26
                    
Install as a Cake Addin
#tool nuget:?package=DKNet.EfCore.Events&version=9.0.26
                    
Install as a Cake Tool

DKNet.EfCore.Events

NuGet NuGet Downloads .NET License

Enhanced Entity Framework Core event-based functionality for implementing domain-driven design (DDD) patterns. This library provides centralized event management, automatic event publishing during EF Core operations, and seamless integration with domain entities.

Features

  • Domain Event Management: Queue and publish domain events from entities
  • Automatic Event Publishing: Events fired automatically during EF Core SaveChanges
  • Event Publisher Abstraction: Central hub for event routing and handling
  • EF Core Hooks Integration: Pre and post-save event triggers
  • Custom Event Handlers: Flexible event handling with dependency injection
  • Entity Event Tracking: Track and manage events at the entity level
  • Exception Handling: Robust error handling for event processing
  • Performance Optimized: Efficient event queuing and batch processing

Supported Frameworks

  • .NET 9.0+
  • Entity Framework Core 9.0+

Installation

Install via NuGet Package Manager:

dotnet add package DKNet.EfCore.Events

Or via Package Manager Console:

Install-Package DKNet.EfCore.Events

Quick Start

Setup Event Publisher

using DKNet.EfCore.Events.Handlers;
using Microsoft.Extensions.DependencyInjection;

// Register event publisher implementation
services.AddEventPublisher<AppDbContext, EventPublisher>();

// Or use your custom implementation
public class CustomEventPublisher : IEventPublisher
{
    public async Task PublishAsync(object eventItem, CancellationToken cancellationToken = default)
    {
        // Custom event publishing logic
        await Task.CompletedTask;
    }
}

services.AddEventPublisher<AppDbContext, CustomEventPublisher>();

Domain Entity with Events

using DKNet.EfCore.Abstractions.Entities;

public class Product : Entity<Guid>
{
    public Product(string name, decimal price, string createdBy) 
        : base(Guid.NewGuid(), createdBy)
    {
        Name = name;
        Price = price;
        
        // Add domain event
        AddEvent(new ProductCreatedEvent(Id, name, price));
    }

    public string Name { get; private set; }
    public decimal Price { get; private set; }
    
    public void UpdatePrice(decimal newPrice, string updatedBy)
    {
        var oldPrice = Price;
        Price = newPrice;
        SetUpdatedBy(updatedBy);
        
        // Add domain event for price change
        AddEvent(new ProductPriceChangedEvent(Id, oldPrice, newPrice));
    }
}

// Domain events
public record ProductCreatedEvent(Guid ProductId, string Name, decimal Price);
public record ProductPriceChangedEvent(Guid ProductId, decimal OldPrice, decimal NewPrice);

Event Handlers

using DKNet.EfCore.Events.Handlers;

public class ProductCreatedHandler : INotificationHandler<ProductCreatedEvent>
{
    private readonly ILogger<ProductCreatedHandler> _logger;
    private readonly IEmailService _emailService;

    public ProductCreatedHandler(ILogger<ProductCreatedHandler> logger, IEmailService emailService)
    {
        _logger = logger;
        _emailService = emailService;
    }

    public async Task Handle(ProductCreatedEvent notification, CancellationToken cancellationToken)
    {
        _logger.LogInformation("Product created: {ProductId} - {Name} (${Price})", 
            notification.ProductId, notification.Name, notification.Price);
            
        // Send notification email
        await _emailService.SendProductCreatedNotificationAsync(notification, cancellationToken);
    }
}

public class ProductPriceChangedHandler : INotificationHandler<ProductPriceChangedEvent>
{
    private readonly IInventoryService _inventoryService;

    public ProductPriceChangedHandler(IInventoryService inventoryService)
    {
        _inventoryService = inventoryService;
    }

    public async Task Handle(ProductPriceChangedEvent notification, CancellationToken cancellationToken)
    {
        // Update inventory records
        await _inventoryService.UpdatePriceAsync(notification.ProductId, notification.NewPrice, cancellationToken);
    }
}

Configuration

DbContext Setup

Events are automatically published during SaveChanges when the event hook is registered:

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

    public DbSet<Product> Products { get; set; }
    public DbSet<Order> Orders { get; set; }

    // Event publishing happens automatically via EventHook
}

Event Handler Registration

// Register event handlers
services.AddScoped<INotificationHandler<ProductCreatedEvent>, ProductCreatedHandler>();
services.AddScoped<INotificationHandler<ProductPriceChangedEvent>, ProductPriceChangedHandler>();

// Or use MediatR for automatic discovery
services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(ProductCreatedHandler).Assembly));

API Reference

Core Interfaces

  • IEventPublisher - Central event publishing abstraction
  • IEventEntity - Interface for entities that can raise domain events (from DKNet.EfCore.Abstractions)
  • EntityEventItem - Wrapper for entity events with metadata

Event Management

  • AddEvent(object) - Queue domain event on entity
  • ClearEvents() - Clear all queued events
  • GetEvents() - Retrieve all queued events

Setup Extensions

  • AddEventPublisher<TDbContext, TImplementation>() - Register event publisher with EF Core hooks

Advanced Usage

Custom Event Publisher

public class MediatREventPublisher : IEventPublisher
{
    private readonly IMediator _mediator;
    private readonly ILogger<MediatREventPublisher> _logger;

    public MediatREventPublisher(IMediator mediator, ILogger<MediatREventPublisher> logger)
    {
        _mediator = mediator;
        _logger = logger;
    }

    public async Task PublishAsync(object eventItem, CancellationToken cancellationToken = default)
    {
        try
        {
            _logger.LogDebug("Publishing event: {EventType}", eventItem.GetType().Name);
            
            if (eventItem is INotification notification)
            {
                await _mediator.Publish(notification, cancellationToken);
            }
            else
            {
                _logger.LogWarning("Event {EventType} does not implement INotification", eventItem.GetType().Name);
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to publish event: {EventType}", eventItem.GetType().Name);
            throw new EventException($"Failed to publish event of type {eventItem.GetType().Name}", ex);
        }
    }
}

Complex Domain Event Scenarios

public class Order : AggregateRoot
{
    private readonly List<OrderItem> _items = [];

    public Order(Guid customerId, string createdBy) : base(createdBy)
    {
        CustomerId = customerId;
        Status = OrderStatus.Pending;
        
        AddEvent(new OrderCreatedEvent(Id, customerId));
    }

    public Guid CustomerId { get; private set; }
    public OrderStatus Status { get; private set; }
    public IReadOnlyList<OrderItem> Items => _items.AsReadOnly();
    public decimal TotalAmount => _items.Sum(i => i.TotalPrice);

    public void AddItem(Guid productId, int quantity, decimal unitPrice)
    {
        var item = new OrderItem(productId, quantity, unitPrice);
        _items.Add(item);
        
        AddEvent(new OrderItemAddedEvent(Id, productId, quantity, unitPrice));
    }

    public void Complete(string updatedBy)
    {
        if (Status != OrderStatus.Pending)
            throw new InvalidOperationException("Only pending orders can be completed");

        Status = OrderStatus.Completed;
        SetUpdatedBy(updatedBy);
        
        AddEvent(new OrderCompletedEvent(Id, CustomerId, TotalAmount, Items.Count));
    }

    public void Cancel(string reason, string updatedBy)
    {
        if (Status == OrderStatus.Completed)
            throw new InvalidOperationException("Completed orders cannot be cancelled");

        Status = OrderStatus.Cancelled;
        SetUpdatedBy(updatedBy);
        
        AddEvent(new OrderCancelledEvent(Id, reason));
    }
}

// Domain events
public record OrderCreatedEvent(Guid OrderId, Guid CustomerId);
public record OrderItemAddedEvent(Guid OrderId, Guid ProductId, int Quantity, decimal UnitPrice);
public record OrderCompletedEvent(Guid OrderId, Guid CustomerId, decimal TotalAmount, int ItemCount);
public record OrderCancelledEvent(Guid OrderId, string Reason);

Event Handler with Side Effects

public class OrderCompletedHandler : INotificationHandler<OrderCompletedEvent>
{
    private readonly IInventoryService _inventoryService;
    private readonly IPaymentService _paymentService;
    private readonly INotificationService _notificationService;
    private readonly ILogger<OrderCompletedHandler> _logger;

    public OrderCompletedHandler(
        IInventoryService inventoryService,
        IPaymentService paymentService,
        INotificationService notificationService,
        ILogger<OrderCompletedHandler> logger)
    {
        _inventoryService = inventoryService;
        _paymentService = paymentService;
        _notificationService = notificationService;
        _logger = logger;
    }

    public async Task Handle(OrderCompletedEvent notification, CancellationToken cancellationToken)
    {
        try
        {
            // Update inventory
            await _inventoryService.ReserveItemsAsync(notification.OrderId, cancellationToken);
            
            // Process payment
            await _paymentService.ProcessPaymentAsync(notification.OrderId, notification.TotalAmount, cancellationToken);
            
            // Send confirmation
            await _notificationService.SendOrderConfirmationAsync(notification.CustomerId, notification.OrderId, cancellationToken);
            
            _logger.LogInformation("Order {OrderId} completed successfully. Total: ${TotalAmount}, Items: {ItemCount}",
                notification.OrderId, notification.TotalAmount, notification.ItemCount);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to process order completion for {OrderId}", notification.OrderId);
            
            // Could add compensating actions or raise error events
            throw new EventException($"Failed to process order completion for {notification.OrderId}", ex);
        }
    }
}

Event Lifecycle

  1. Event Creation: Domain events are added to entities during business operations
  2. Event Queuing: Events are stored in entity's event collection until SaveChanges
  3. Event Publishing: Events are automatically published during EF Core SaveChanges via EventHook
  4. Event Handling: Registered event handlers process the events asynchronously
  5. Event Cleanup: Successfully processed events are cleared from entities

Error Handling

public class RobustEventPublisher : IEventPublisher
{
    private readonly IMediator _mediator;
    private readonly ILogger<RobustEventPublisher> _logger;

    public async Task PublishAsync(object eventItem, CancellationToken cancellationToken = default)
    {
        var maxRetries = 3;
        var retryDelay = TimeSpan.FromMilliseconds(100);

        for (int attempt = 1; attempt <= maxRetries; attempt++)
        {
            try
            {
                await _mediator.Publish((INotification)eventItem, cancellationToken);
                return;
            }
            catch (Exception ex) when (attempt < maxRetries)
            {
                _logger.LogWarning(ex, "Event publishing failed on attempt {Attempt} for {EventType}. Retrying...", 
                    attempt, eventItem.GetType().Name);
                    
                await Task.Delay(retryDelay * attempt, cancellationToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Event publishing failed after {MaxRetries} attempts for {EventType}", 
                    maxRetries, eventItem.GetType().Name);
                throw new EventException($"Failed to publish event after {maxRetries} attempts", ex);
            }
        }
    }
}

Performance Considerations

  • Batch Processing: Events are published in batches during SaveChanges
  • Async Handlers: All event handlers should be async for non-blocking execution
  • Memory Management: Events are cleared after successful publishing
  • Transaction Scope: Events are published within the same transaction as data changes

Best Practices

  • Single Responsibility: Keep event handlers focused on one concern
  • Idempotency: Design event handlers to be idempotent
  • Error Isolation: Don't let event handler failures affect the main transaction
  • Event Versioning: Plan for event schema evolution
  • Testing: Test event handlers independently from entities

Contributing

See the main CONTRIBUTING.md for guidelines on how to contribute to this project.

License

This project is licensed under the MIT License.


Part of the DKNet Framework - A comprehensive .NET framework for building modern, scalable applications.

Product Compatible and additional computed target framework versions.
.NET 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 was computed.  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
9.0.26 26 9/16/2025
9.0.25 21 9/15/2025
9.0.24 19 9/15/2025
9.0.23 96 9/6/2025
9.0.22 160 9/3/2025
9.0.21 130 9/1/2025
9.0.20 144 7/15/2025
9.0.19 139 7/14/2025
9.0.18 140 7/14/2025
9.0.17 139 7/14/2025
9.0.16 122 7/11/2025
9.0.15 123 7/11/2025
9.0.14 121 7/11/2025
9.0.13 130 7/11/2025
9.0.12 148 7/8/2025
9.0.11 143 7/8/2025
9.0.10 138 7/7/2025
9.0.9 140 7/2/2025
9.0.7 147 7/1/2025
9.0.6 146 6/30/2025
9.0.5 140 6/24/2025
9.0.4 140 6/24/2025
9.0.3 146 6/23/2025
9.0.2 141 6/23/2025
9.0.1 150 6/23/2025