diff --git a/Readme.md b/Readme.md index 186e7dd..cc6c878 100644 --- a/Readme.md +++ b/Readme.md @@ -28,10 +28,21 @@ Install-Package Pandatech.EFCore.AuditBase 1. Inherit from `AuditEntityBase` in your entity classes to enable auditing. 2. Use `MarkAsUpdated(userId)` and `MarkAsDeleted(userId)` methods to handle entity updates and deletions. -3. Apply `DbContextExtensions.UseAuditPropertyValidation` in your DbContext to enforce audit method usage. +3. Apply `OptionsBuilderExtensions.UseAuditBaseValidatorInterceptor` during the DbContext registration in your DI configuration. 4. Leverage `ModelBuilderExtensions.FilterOutDeletedMarkedObjects` to automatically exclude soft-deleted entities from EF Core queries. +### Registering DbContext with `UseAuditBaseValidatorInterceptor`: + +When configuring your `DbContext`, ensure you call `UseAuditBaseValidatorInterceptor` during the service registration phase: +```csharp +var connectionString = configuration.GetConnectionString("Postgres"); + +builder.Services.AddDbContextPool(options => + options.UseNpgsql(connectionString) + .UseAuditBaseValidatorInterceptor()); // Enforce audit method usage +``` + ### Entity Inheritance Example: Assuming you have a `Product` entity in your application, you would inherit from `AuditEntityBase` to include auditing @@ -73,8 +84,7 @@ public void DeleteProduct(Product product, long deletingUserId) ### DbContext Configuration: -In your `DbContext`, ensure you call `UseAuditPropertyValidation` to enforce the usage of audit methods and -`FilterOutDeletedMarkedObjects` to apply the global filter for soft-deleted entities: +In your `DbContext`, you can use the `FilterOutDeletedMarkedObjects` method to apply a global query filter for soft-deleted entities: ```csharp using Microsoft.EntityFrameworkCore; @@ -90,11 +100,6 @@ public class MyDbContext : DbContext modelBuilder.FilterOutDeletedMarkedObjects(); // Apply global query filter for soft deletes } - - public MyDbContext(DbContextOptions options) : base(options) - { - this.UseAuditPropertyValidation(); // Enforce audit method usage - } } ``` diff --git a/src/EFCore.AuditBase/DbContextExtension.cs b/src/EFCore.AuditBase/DbContextExtension.cs deleted file mode 100644 index f5c6840..0000000 --- a/src/EFCore.AuditBase/DbContextExtension.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Microsoft.EntityFrameworkCore; - -namespace EFCore.AuditBase; - -public static class DbContextExtensions -{ - public static void UseAuditPropertyValidation(this DbContext context) - { - context.SavingChanges += (sender, e) => - { - var dbContext = (DbContext)sender!; - ValidateAuditMethodUsage(dbContext); - }; - } - - private static void ValidateAuditMethodUsage(DbContext context) - { - var entries = context.ChangeTracker - .Entries() - .Where(e => e.State is EntityState.Modified); - - foreach (var entry in entries) - { - var entityName = entry.Entity.GetType() - .Name; - - if (entry.State is not EntityState.Modified) - { - continue; - } - - var originalVersion = entry.OriginalValues[nameof(AuditEntityBase.Version)] as int?; - var currentVersion = entry.CurrentValues[nameof(AuditEntityBase.Version)] as int?; - - if (originalVersion == currentVersion) - { - throw new InvalidOperationException( - $"Entity '{entityName}' must be updated using MarkAsUpdated method. Missing or incorrect audit fields for update."); - } - } - } -} \ No newline at end of file diff --git a/src/EFCore.AuditBase/EFCore.AuditBase.csproj b/src/EFCore.AuditBase/EFCore.AuditBase.csproj index 07ba769..0cb7aec 100644 --- a/src/EFCore.AuditBase/EFCore.AuditBase.csproj +++ b/src/EFCore.AuditBase/EFCore.AuditBase.csproj @@ -8,13 +8,13 @@ Readme.md Pandatech MIT - 2.0.0 + 3.0.0 Pandatech.EFCore.AuditBase Pandatech EFCore AuditBase Pandatech, library, audit, optimistic lock, tracking, efcore, soft delete, versioning Pandatech.EFCore.AuditBase provides a robust auditing solution for EF Core applications, ensuring traceability and integrity of entity modifications. It seamlessly integrates auditing capabilities into your EF Core entities, enforcing best practices for entity state changes, deletion handling, and versioning to support concurrency control. Ideal for applications requiring a reliable audit trail and compliance with data handling standards. https://github.com/PandaTechAM/be-lib-efcore-audit-base - .Net 9 Upgrade + Changed registration way. Implementation is the same. Check Readme.md diff --git a/src/EFCore.AuditBase/Extensions/DbOptionExtensions.cs b/src/EFCore.AuditBase/Extensions/DbOptionExtensions.cs new file mode 100644 index 0000000..74677b4 --- /dev/null +++ b/src/EFCore.AuditBase/Extensions/DbOptionExtensions.cs @@ -0,0 +1,14 @@ +using Microsoft.EntityFrameworkCore; +using EFCore.AuditBase.Interceptors; + +namespace EFCore.AuditBase.Extensions; + +public static class OptionsBuilderExtensions +{ + public static DbContextOptionsBuilder UseAuditBaseValidatorInterceptor(this DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.AddInterceptors(new AuditPropertyValidationInterceptor()); + + return optionsBuilder; + } +} \ No newline at end of file diff --git a/src/EFCore.AuditBase/Interceptors/AuditPropertyValidationInterceptor.cs b/src/EFCore.AuditBase/Interceptors/AuditPropertyValidationInterceptor.cs new file mode 100644 index 0000000..c40e7de --- /dev/null +++ b/src/EFCore.AuditBase/Interceptors/AuditPropertyValidationInterceptor.cs @@ -0,0 +1,57 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; + +namespace EFCore.AuditBase.Interceptors; + +internal class AuditPropertyValidationInterceptor : SaveChangesInterceptor +{ + public override InterceptionResult SavingChanges(DbContextEventData eventData, + InterceptionResult result) + { + if (eventData.Context is not null) + { + ValidateAuditMethodUsage(eventData.Context); + } + + return base.SavingChanges(eventData, result); + } + + public override ValueTask> SavingChangesAsync(DbContextEventData eventData, + InterceptionResult result, + CancellationToken cancellationToken = default) + { + if (eventData.Context is not null) + { + ValidateAuditMethodUsage(eventData.Context); + } + + return base.SavingChangesAsync(eventData, result, cancellationToken); + } + + private static void ValidateAuditMethodUsage(DbContext context) + { + var entries = context.ChangeTracker + .Entries() + .Where(e => e.State == EntityState.Modified); + + foreach (var entry in entries) + { + var entityName = entry.Entity.GetType() + .Name; + + if (entry.State is not EntityState.Modified) + { + continue; + } + + var originalVersion = entry.OriginalValues[nameof(AuditEntityBase.Version)] as int?; + var currentVersion = entry.CurrentValues[nameof(AuditEntityBase.Version)] as int?; + + if (originalVersion == currentVersion) + { + throw new InvalidOperationException( + $"Entity '{entityName}' must be updated using MarkAsUpdated method. Missing or incorrect audit fields for update."); + } + } + } +} \ No newline at end of file