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
<PackageReference Include="DKNet.EfCore.Events" Version="9.0.26" />
<PackageVersion Include="DKNet.EfCore.Events" Version="9.0.26" />
<PackageReference Include="DKNet.EfCore.Events" />
paket add DKNet.EfCore.Events --version 9.0.26
#r "nuget: DKNet.EfCore.Events, 9.0.26"
#:package DKNet.EfCore.Events@9.0.26
#addin nuget:?package=DKNet.EfCore.Events&version=9.0.26
#tool nuget:?package=DKNet.EfCore.Events&version=9.0.26
DKNet.EfCore.Events
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 abstractionIEventEntity
- 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 entityClearEvents()
- Clear all queued eventsGetEvents()
- 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
- Event Creation: Domain events are added to entities during business operations
- Event Queuing: Events are stored in entity's event collection until SaveChanges
- Event Publishing: Events are automatically published during EF Core SaveChanges via EventHook
- Event Handling: Registered event handlers process the events asynchronously
- 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.
Related Packages
- DKNet.EfCore.Abstractions - Core abstractions including IEventEntity
- DKNet.EfCore.Extensions - EF Core functionality extensions
- DKNet.EfCore.Hooks - EF Core lifecycle hooks (used internally)
- DKNet.SlimBus.Extensions - Alternative CQRS event handling
Part of the DKNet Framework - A comprehensive .NET framework for building modern, scalable applications.
Product | Versions 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. |
-
net9.0
- DKNet.EfCore.Abstractions (>= 9.0.26)
- DKNet.EfCore.Hooks (>= 9.0.26)
- FluentResults (>= 4.0.0)
- Mapster (>= 7.4.0)
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 |