DKNet is an enterprise-grade .NET library collection focused on advanced EF Core extensions, dynamic predicate building, and the Specification pattern. It provides production-ready tools for building robust, type-safe, and testable data access layers, including dynamic LINQ support, LinqKit integration. Designed for modern cloud-native applications, DKNet enforces strict code quality, async best practices, and full documentation for all public APIs. Enterprise-grade .NET library suite for modern application development, featuring advanced EF Core extensions (dynamic predicates, specifications, LinqKit), robust Domain-Driven Design (DDD) patterns, and domain event support. DKNet empowers scalable, maintainable, and testable solutions with type-safe validation, async/await, XML documentation, and high code quality standards. Ideal for cloud-native, microservices, and enterprise architectures.
$ dotnet add package DKNet.EfCore.EventsEnhanced 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.
Install via NuGet Package Manager:
dotnet add package DKNet.EfCore.Events
Or via Package Manager Console:
Install-Package DKNet.EfCore.Events
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>();
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);
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);
}
}
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
}
// 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));
IEventPublisher - Central event publishing abstractionIEventEntity - Interface for entities that can raise domain events (from DKNet.EfCore.Abstractions)EntityEventItem - Wrapper for entity events with metadataAddEvent(object) - Queue domain event on entityClearEvents() - Clear all queued eventsGetEvents() - Retrieve all queued eventsAddEventPublisher<TDbContext, TImplementation>() - Register event publisher with EF Core hookspublic 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);
}
}
}
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);
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);
}
}
}
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);
}
}
}
}
See the main CONTRIBUTING.md for guidelines on how to contribute to this project.
This project is licensed under the MIT License.
Part of the DKNet Framework - A comprehensive .NET framework for building modern, scalable applications.