VOOZH about

URL: https://www.nuget.org/packages/DKNet.EfCore.Hooks

⇱ NuGet Gallery | DKNet.EfCore.Hooks 10.0.27




👁 Image
DKNet.EfCore.Hooks 10.0.27

dotnet add package DKNet.EfCore.Hooks --version 10.0.27
 
 
NuGet\Install-Package DKNet.EfCore.Hooks -Version 10.0.27
 
 
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.Hooks" Version="10.0.27" />
 
 
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DKNet.EfCore.Hooks" Version="10.0.27" />
 
Directory.Packages.props
<PackageReference Include="DKNet.EfCore.Hooks" />
 
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.Hooks --version 10.0.27
 
 
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
#r "nuget: DKNet.EfCore.Hooks, 10.0.27"
 
 
#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.Hooks@10.0.27
 
 
#: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.Hooks&version=10.0.27
 
Install as a Cake Addin
#tool nuget:?package=DKNet.EfCore.Hooks&version=10.0.27
 
Install as a Cake Tool
The NuGet Team does not provide support for this client. Please contact its maintainers for support.

DKNet.EfCore.Hooks

👁 NuGet
👁 NuGet Downloads
👁 .NET

Entity Framework Core lifecycle hooks system providing pre and post-save interceptors for implementing cross-cutting concerns like auditing, validation, caching, and event publishing. This package enables clean separation of business logic from data access concerns.

Features

  • Lifecycle Hooks: Pre-save and post-save hooks for Entity Framework Core operations
  • Snapshot Context: Track entity changes with before/after state comparison
  • Async Support: Full async/await support for non-blocking hook execution
  • Dependency Injection: Seamless integration with .NET dependency injection
  • Multiple Hooks: Support for multiple hooks per DbContext with execution ordering
  • Change Tracking: Access to entity state changes during save operations
  • Error Handling: Robust error handling and hook execution management
  • Performance Optimized: Efficient execution with minimal overhead

Supported Frameworks

  • .NET 9.0+
  • Entity Framework Core 9.0+

Installation

Install via NuGet Package Manager:

dotnet add package DKNet.EfCore.Hooks

Or via Package Manager Console:

Install-Package DKNet.EfCore.Hooks

Quick Start

Basic Hook Implementation

using DKNet.EfCore.Hooks;
using DKNet.EfCore.Extensions.Snapshots;

// Audit hook example
public class AuditHook : IBeforeSaveHookAsync
{
 private readonly ICurrentUserService _currentUserService;

 public AuditHook(ICurrentUserService currentUserService)
 {
 _currentUserService = currentUserService;
 }

 public Task RunBeforeSaveAsync(SnapshotContext context, CancellationToken cancellationToken = default)
 {
 var currentUser = _currentUserService.UserId;
 var now = DateTimeOffset.UtcNow;

 foreach (var entry in context.Entries)
 {
 if (entry.Entity is IAuditedProperties auditedEntity)
 {
 switch (entry.State)
 {
 case EntityState.Added:
 auditedEntity.CreatedBy = currentUser;
 auditedEntity.CreatedOn = now;
 break;
 case EntityState.Modified:
 auditedEntity.UpdatedBy = currentUser;
 auditedEntity.UpdatedOn = now;
 break;
 }
 }
 }

 return Task.CompletedTask;
 }
}

// Event publishing hook
public class EventPublishingHook : IAfterSaveHookAsync
{
 private readonly IEventPublisher _eventPublisher;

 public EventPublishingHook(IEventPublisher eventPublisher)
 {
 _eventPublisher = eventPublisher;
 }

 public async Task RunAfterSaveAsync(SnapshotContext context, CancellationToken cancellationToken = default)
 {
 foreach (var entry in context.Entries)
 {
 if (entry.Entity is IEventEntity eventEntity)
 {
 var events = eventEntity.GetEvents();
 foreach (var domainEvent in events)
 {
 await _eventPublisher.PublishAsync(domainEvent, cancellationToken);
 }
 eventEntity.ClearEvents();
 }
 }
 }
}

Setup and Registration

using Microsoft.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore;

public class AppDbContext : DbContext
{
 public DbSet<Product> Products { get; set; }
 public DbSet<Customer> Customers { get; set; }

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

// Configure services
public void ConfigureServices(IServiceCollection services)
{
 // Register hooks
 services.AddHook<AppDbContext, AuditHook>();
 services.AddHook<AppDbContext, EventPublishingHook>();
 services.AddHook<AppDbContext, ValidationHook>();

 // Register hook dependencies
 services.AddScoped<ICurrentUserService, CurrentUserService>();
 services.AddScoped<IEventPublisher, EventPublisher>();

 // Add DbContext with hooks
 services.AddDbContext<AppDbContext>((provider, options) =>
 {
 options.UseSqlServer(connectionString)
 .AddHookInterceptor<AppDbContext>(provider);
 });
}

Combined Hook Implementation

public class ComprehensiveHook : IHookAsync
{
 private readonly ILogger<ComprehensiveHook> _logger;
 private readonly IValidator _validator;
 private readonly IEventPublisher _eventPublisher;

 public ComprehensiveHook(
 ILogger<ComprehensiveHook> logger,
 IValidator validator,
 IEventPublisher eventPublisher)
 {
 _logger = logger;
 _validator = validator;
 _eventPublisher = eventPublisher;
 }

 public async Task RunBeforeSaveAsync(SnapshotContext context, CancellationToken cancellationToken = default)
 {
 _logger.LogDebug("Running pre-save hooks for {EntityCount} entities", context.Entries.Count);

 // Validation
 foreach (var entry in context.Entries)
 {
 if (entry.State == EntityState.Added || entry.State == EntityState.Modified)
 {
 var validationResult = await _validator.ValidateAsync(entry.Entity, cancellationToken);
 if (!validationResult.IsValid)
 {
 throw new ValidationException($"Validation failed for {entry.Entity.GetType().Name}: {validationResult.Errors}");
 }
 }
 }

 // Auto-set timestamps
 foreach (var entry in context.Entries)
 {
 if (entry.Entity is ITimestampedEntity timestamped)
 {
 if (entry.State == EntityState.Added)
 timestamped.CreatedAt = DateTimeOffset.UtcNow;
 if (entry.State == EntityState.Modified)
 timestamped.UpdatedAt = DateTimeOffset.UtcNow;
 }
 }
 }

 public async Task RunAfterSaveAsync(SnapshotContext context, CancellationToken cancellationToken = default)
 {
 _logger.LogDebug("Running post-save hooks for {EntityCount} entities", context.Entries.Count);

 // Publish domain events
 var events = new List<object>();
 foreach (var entry in context.Entries)
 {
 if (entry.Entity is IEventEntity eventEntity)
 {
 events.AddRange(eventEntity.GetEvents());
 eventEntity.ClearEvents();
 }
 }

 foreach (var domainEvent in events)
 {
 await _eventPublisher.PublishAsync(domainEvent, cancellationToken);
 }

 // Cache invalidation
 foreach (var entry in context.Entries)
 {
 if (entry.State == EntityState.Modified || entry.State == EntityState.Deleted)
 {
 // Invalidate cache for this entity type
 await InvalidateCacheForEntityType(entry.Entity.GetType(), cancellationToken);
 }
 }
 }

 private async Task InvalidateCacheForEntityType(Type entityType, CancellationToken cancellationToken)
 {
 // Implementation depends on your caching strategy
 _logger.LogDebug("Invalidating cache for entity type {EntityType}", entityType.Name);
 await Task.CompletedTask;
 }
}

Configuration

Multiple Hooks with Ordering

public class OrderedValidationHook : IBeforeSaveHookAsync
{
 public int Order => 1; // Run first

 public Task RunBeforeSaveAsync(SnapshotContext context, CancellationToken cancellationToken = default)
 {
 // Validation logic
 return Task.CompletedTask;
 }
}

public class OrderedAuditHook : IBeforeSaveHookAsync
{
 public int Order => 2; // Run after validation

 public Task RunBeforeSaveAsync(SnapshotContext context, CancellationToken cancellationToken = default)
 {
 // Audit logic
 return Task.CompletedTask;
 }
}

// Register in order
services.AddHook<AppDbContext, OrderedValidationHook>();
services.AddHook<AppDbContext, OrderedAuditHook>();

Conditional Hook Execution

public class ConditionalHook : IBeforeSaveHookAsync
{
 private readonly IFeatureManager _featureManager;

 public ConditionalHook(IFeatureManager featureManager)
 {
 _featureManager = featureManager;
 }

 public async Task RunBeforeSaveAsync(SnapshotContext context, CancellationToken cancellationToken = default)
 {
 if (!await _featureManager.IsEnabledAsync("AuditLogging"))
 return;

 // Execute hook logic only when feature is enabled
 foreach (var entry in context.Entries)
 {
 // Conditional audit logic
 }
 }
}

API Reference

Hook Interfaces

  • IHookBaseAsync - Base interface for all hooks
  • IBeforeSaveHookAsync - Pre-save hook interface
  • IAfterSaveHookAsync - Post-save hook interface
  • IHookAsync - Combined pre and post-save hook interface

Setup Extensions

  • AddHook<TDbContext, THook>() - Register hook for specific DbContext
  • AddHookInterceptor<TDbContext>(IServiceProvider) - Add hook interceptor to DbContext options

Snapshot Context

  • SnapshotContext.Entries - Collection of entity change entries
  • SnapshotEntityEntry.Entity - The tracked entity
  • SnapshotEntityEntry.State - Entity state (Added, Modified, Deleted, etc.)
  • SnapshotEntityEntry.OriginalValues - Original property values (for Modified entities)
  • SnapshotEntityEntry.CurrentValues - Current property values

Advanced Usage

Performance Monitoring Hook

public class PerformanceMonitoringHook : IHookAsync
{
 private readonly ILogger<PerformanceMonitoringHook> _logger;
 private readonly IMetrics _metrics;
 private readonly Stopwatch _stopwatch = new();

 public PerformanceMonitoringHook(ILogger<PerformanceMonitoringHook> logger, IMetrics metrics)
 {
 _logger = logger;
 _metrics = metrics;
 }

 public Task RunBeforeSaveAsync(SnapshotContext context, CancellationToken cancellationToken = default)
 {
 _stopwatch.Restart();
 _logger.LogDebug("Starting save operation for {EntityCount} entities", context.Entries.Count);
 
 _metrics.Counter("efcore.save_operations.started").Increment();
 _metrics.Histogram("efcore.entities_per_save").Record(context.Entries.Count);

 return Task.CompletedTask;
 }

 public Task RunAfterSaveAsync(SnapshotContext context, CancellationToken cancellationToken = default)
 {
 _stopwatch.Stop();
 var duration = _stopwatch.ElapsedMilliseconds;

 _logger.LogDebug("Completed save operation in {Duration}ms for {EntityCount} entities", 
 duration, context.Entries.Count);

 _metrics.Histogram("efcore.save_operations.duration").Record(duration);
 _metrics.Counter("efcore.save_operations.completed").Increment();

 if (duration > 5000) // Log slow operations
 {
 _logger.LogWarning("Slow save operation detected: {Duration}ms for {EntityCount} entities", 
 duration, context.Entries.Count);
 }

 return Task.CompletedTask;
 }
}

Security and Authorization Hook

public class SecurityHook : IBeforeSaveHookAsync
{
 private readonly ICurrentUserService _currentUser;
 private readonly IAuthorizationService _authorizationService;

 public SecurityHook(ICurrentUserService currentUser, IAuthorizationService authorizationService)
 {
 _currentUser = currentUser;
 _authorizationService = authorizationService;
 }

 public async Task RunBeforeSaveAsync(SnapshotContext context, CancellationToken cancellationToken = default)
 {
 foreach (var entry in context.Entries)
 {
 var entityType = entry.Entity.GetType().Name;
 var operation = GetOperationType(entry.State);

 var authResult = await _authorizationService.AuthorizeAsync(
 _currentUser.Principal, 
 entry.Entity, 
 $"{entityType}.{operation}");

 if (!authResult.Succeeded)
 {
 throw new UnauthorizedAccessException(
 $"User {_currentUser.UserId} is not authorized to {operation} {entityType}");
 }

 // Row-level security for owned entities
 if (entry.Entity is IOwnedEntity ownedEntity)
 {
 if (ownedEntity.OwnerId != _currentUser.UserId && !_currentUser.IsAdmin)
 {
 throw new UnauthorizedAccessException(
 $"User {_currentUser.UserId} cannot access entity owned by {ownedEntity.OwnerId}");
 }
 }
 }
 }

 private static string GetOperationType(EntityState state) => state switch
 {
 EntityState.Added => "Create",
 EntityState.Modified => "Update",
 EntityState.Deleted => "Delete",
 _ => "Read"
 };
}

Integration with External Systems

public class ExternalIntegrationHook : IAfterSaveHookAsync
{
 private readonly ISearchIndexService _searchService;
 private readonly INotificationService _notificationService;
 private readonly ILogger<ExternalIntegrationHook> _logger;

 public ExternalIntegrationHook(
 ISearchIndexService searchService,
 INotificationService notificationService,
 ILogger<ExternalIntegrationHook> logger)
 {
 _searchService = searchService;
 _notificationService = notificationService;
 _logger = logger;
 }

 public async Task RunAfterSaveAsync(SnapshotContext context, CancellationToken cancellationToken = default)
 {
 var searchUpdateTasks = new List<Task>();
 var notificationTasks = new List<Task>();

 foreach (var entry in context.Entries)
 {
 try
 {
 // Update search index
 if (entry.Entity is ISearchable searchableEntity)
 {
 var task = entry.State switch
 {
 EntityState.Added or EntityState.Modified => 
 _searchService.IndexAsync(searchableEntity, cancellationToken),
 EntityState.Deleted => 
 _searchService.RemoveAsync(searchableEntity.Id, cancellationToken),
 _ => Task.CompletedTask
 };
 searchUpdateTasks.Add(task);
 }

 // Send notifications
 if (entry.Entity is INotifiable notifiableEntity && entry.State == EntityState.Added)
 {
 var notificationTask = _notificationService.SendCreatedNotificationAsync(
 notifiableEntity, cancellationToken);
 notificationTasks.Add(notificationTask);
 }
 }
 catch (Exception ex)
 {
 _logger.LogError(ex, "Error processing external integration for {EntityType} {EntityId}", 
 entry.Entity.GetType().Name, GetEntityId(entry.Entity));
 }
 }

 // Execute all tasks concurrently
 await Task.WhenAll(searchUpdateTasks.Concat(notificationTasks));
 }

 private static object? GetEntityId(object entity)
 {
 return entity.GetType().GetProperty("Id")?.GetValue(entity);
 }
}

Error Handling and Resilience

public class ResilientHook : IHookAsync
{
 private readonly ILogger<ResilientHook> _logger;
 private readonly IRetryPolicy _retryPolicy;

 public ResilientHook(ILogger<ResilientHook> logger, IRetryPolicy retryPolicy)
 {
 _logger = logger;
 _retryPolicy = retryPolicy;
 }

 public async Task RunBeforeSaveAsync(SnapshotContext context, CancellationToken cancellationToken = default)
 {
 try
 {
 // Pre-save logic with retry
 await _retryPolicy.ExecuteAsync(async () =>
 {
 await ProcessPreSaveLogic(context, cancellationToken);
 });
 }
 catch (Exception ex)
 {
 _logger.LogError(ex, "Critical error in pre-save hook. Operation will be aborted.");
 throw; // Re-throw to prevent save operation
 }
 }

 public async Task RunAfterSaveAsync(SnapshotContext context, CancellationToken cancellationToken = default)
 {
 try
 {
 // Post-save logic with resilience (don't fail the main operation)
 await _retryPolicy.ExecuteAsync(async () =>
 {
 await ProcessPostSaveLogic(context, cancellationToken);
 });
 }
 catch (Exception ex)
 {
 // Log error but don't re-throw to avoid affecting the main save operation
 _logger.LogError(ex, "Error in post-save hook. Main operation completed successfully.");
 }
 }

 private Task ProcessPreSaveLogic(SnapshotContext context, CancellationToken cancellationToken)
 {
 // Critical pre-save operations
 return Task.CompletedTask;
 }

 private Task ProcessPostSaveLogic(SnapshotContext context, CancellationToken cancellationToken)
 {
 // Non-critical post-save operations
 return Task.CompletedTask;
 }
}

Best Practices

  • Separation of Concerns: Keep hooks focused on single responsibilities
  • Error Handling: Use try-catch in post-save hooks to avoid affecting main operations
  • Performance: Minimize processing time in pre-save hooks
  • Async Operations: Use async/await for I/O operations
  • Logging: Add comprehensive logging for debugging and monitoring
  • Testing: Mock hook dependencies for unit testing

Performance Considerations

  • Hook Execution Order: Critical hooks should run first
  • Async Operations: Use Task.WhenAll for concurrent operations
  • Database Calls: Minimize additional database calls in hooks
  • Memory Usage: Be mindful of memory usage when processing large change sets
  • Caching: Consider caching expensive operations within hook scope

Thread Safety

  • Hook instances are scoped to the DbContext instance
  • Concurrent access to shared resources requires proper synchronization
  • Use thread-safe services and avoid shared mutable state
  • Entity Framework Core change tracking is not thread-safe

Contributing

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

License

This project is licensed under the .

Related Packages

  • - EF Core functionality extensions (includes SnapshotContext)
  • - Domain event handling (uses hooks internally)
  • - Core entity abstractions
  • - Data authorization patterns

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

Product Versions Compatible and additional computed target framework versions.
.NET net10.0 net10.0 is compatible.  net10.0-android net10.0-android was computed.  net10.0-browser net10.0-browser was computed.  net10.0-ios net10.0-ios was computed.  net10.0-maccatalyst net10.0-maccatalyst was computed.  net10.0-macos net10.0-macos was computed.  net10.0-tvos net10.0-tvos was computed.  net10.0-windows 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 (3)

Showing the top 3 NuGet packages that depend on DKNet.EfCore.Hooks:

Package Downloads
DKNet.EfCore.Events

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.

DKNet.EfCore.DataAuthorization

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.

DKNet.EfCore.AuditLogs

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.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
10.0.27 182 5/22/2026
10.0.26 141 5/19/2026
10.0.25 443 3/27/2026
10.0.24 138 3/27/2026
10.0.23 144 3/27/2026
10.0.22 135 3/26/2026
10.0.21 186 3/17/2026
10.0.20 154 2/2/2026
10.0.19 301 1/21/2026
10.0.18 147 1/21/2026
10.0.17 173 1/19/2026
10.0.16 156 1/18/2026
10.0.15 162 1/18/2026
10.0.14 147 1/18/2026
10.0.13 144 1/17/2026
10.0.12 148 1/17/2026
10.0.11 154 1/17/2026
10.0.10 146 1/17/2026
10.0.9 164 1/16/2026
10.0.8 157 1/16/2026
Loading failed