Pandatech.EFCore.AuditBase
5.0.0
dotnet add package Pandatech.EFCore.AuditBase --version 5.0.0
NuGet\Install-Package Pandatech.EFCore.AuditBase -Version 5.0.0
<PackageReference Include="Pandatech.EFCore.AuditBase" Version="5.0.0" />
<PackageVersion Include="Pandatech.EFCore.AuditBase" Version="5.0.0" />
<PackageReference Include="Pandatech.EFCore.AuditBase" />
paket add Pandatech.EFCore.AuditBase --version 5.0.0
#r "nuget: Pandatech.EFCore.AuditBase, 5.0.0"
#:package Pandatech.EFCore.AuditBase@5.0.0
#addin nuget:?package=Pandatech.EFCore.AuditBase&version=5.0.0
#tool nuget:?package=Pandatech.EFCore.AuditBase&version=5.0.0
Pandatech.EFCore.AuditBase
Auditing base for EF Core entities. Inherit one class and get automatic CreatedAt/UpdatedAt/UserId tracking,
soft delete, optimistic concurrency via row versioning, bulk update/delete helpers, and a SaveChanges interceptor
that enforces correct audit method usage at runtime.
Targets net8.0, net9.0, and net10.0.
Table of Contents
- Features
- Installation
- Getting Started
- AuditEntityBase
- Registering the Interceptor
- Soft Delete Query Filter
- Bulk Operations
- Concurrency Handling
- SyncAuditBase
Features
- Automatic audit fields —
CreatedAt,CreatedByUserId,UpdatedAt,UpdatedByUserId,Deleted,Versionmaintained on every entity that inheritsAuditEntityBase - Enforced audit methods — a
SaveChangesinterceptor throws at runtime if a modified entity'sVersionwas not incremented, meaning someone bypassedMarkAsUpdated/MarkAsDeleted - Soft delete —
Deletedflag withMarkAsDeletedand a global query filter that hides deleted rows transparently - Optimistic concurrency —
Versionis decorated with[ConcurrencyCheck]; EF Core raises a concurrency exception on conflict automatically - Bulk helpers —
ExecuteSoftDeleteAsyncandExecuteUpdateAndMarkUpdatedAsynctranslate directly toExecuteUpdateAsyncdatabase calls while still maintaining correct audit fields - In-memory batch delete —
MarkAsDeletedoverload onIEnumerable<T>for cases where entities are already tracked
Installation
dotnet add package Pandatech.EFCore.AuditBase
Getting Started
Inherit AuditEntityBase in your entity:
public class Product : AuditEntityBase
{
public long Id { get; set; }
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
}
That's enough to gain all audit properties. Wire up the interceptor and query filter as shown below.
AuditEntityBase
CreatedAt DateTime Set to UtcNow on construction. Never modified after that.
CreatedByUserId long? Required on construction (required init). Never modified after that.
UpdatedAt DateTime? Set by MarkAsUpdated / MarkAsDeleted.
UpdatedByUserId long? Set by MarkAsUpdated / MarkAsDeleted.
Deleted bool Set to true by MarkAsDeleted.
Version int Starts at 1. Incremented by every MarkAsUpdated / MarkAsDeleted call.
MarkAsUpdated
product.MarkAsUpdated(userId);
// or with an explicit timestamp:
product.MarkAsUpdated(userId, updatedAt: syncedTime);
await dbContext.SaveChangesAsync(ct);
MarkAsDeleted
product.MarkAsDeleted(userId);
await dbContext.SaveChangesAsync(ct);
Both methods increment Version. The interceptor validates this increment on every SaveChanges call — if you modify
an audited entity's properties directly without calling MarkAsUpdated, the interceptor throws:
InvalidOperationException: Entity 'Product' was modified without calling MarkAsUpdated or MarkAsDeleted.
Registering the Interceptor
builder.Services.AddDbContextPool<AppDbContext>(options =>
options.UseNpgsql(connectionString)
.UseAuditBaseValidatorInterceptor());
UseAuditBaseValidatorInterceptor adds AuditPropertyValidationInterceptor to the context. It hooks into both
SavingChanges and SavingChangesAsync.
Soft Delete Query Filter
Apply a global query filter in OnModelCreating to exclude soft-deleted rows from all queries automatically:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.FilterOutDeletedMarkedObjects();
}
FilterOutDeletedMarkedObjects iterates every entity type that inherits AuditEntityBase and applies
.HasQueryFilter(e => !e.Deleted) to each one via expression trees.
To include deleted rows in a specific query:
var all = await dbContext.Products.IgnoreQueryFilters().ToListAsync(ct);
Bulk Operations
ExecuteSoftDeleteAsync
Soft-deletes all rows matching a query in a single UPDATE statement. Does not load entities into memory.
await dbContext.Products
.Where(p => p.Price > 100)
.ExecuteSoftDeleteAsync(userId, ct: ct);
Translates to:
UPDATE products
SET deleted = true, updated_at = NOW(), updated_by_user_id = @userId, version = version + 1
WHERE price > 100
ExecuteUpdateAndMarkUpdatedAsync
Updates arbitrary properties while automatically maintaining UpdatedAt, UpdatedByUserId, and Version:
await dbContext.Products
.Where(p => p.Price > 100)
.ExecuteUpdateAndMarkUpdatedAsync(
userId,
x => x.SetProperty(p => p.Price, p => p.Price * 0.9m),
ct);
MarkAsDeleted (in-memory batch)
For already-tracked entities where you want to call SaveChanges once after marking several:
var products = await dbContext.Products.Where(p => p.Price > 100).ToListAsync(ct);
products.MarkAsDeleted(userId);
await dbContext.SaveChangesAsync(ct);
Optimistic locking note:
ExecuteSoftDeleteAsyncandExecuteUpdateAndMarkUpdatedAsyncbypass EF Core's change tracker and do not raise concurrency exceptions. They incrementVersionunconditionally. Use them when bulk throughput matters more than per-row conflict detection.
Concurrency Handling
Version is decorated with [ConcurrencyCheck]. When two requests load the same entity and both call SaveChanges,
the second one gets a DbUpdateConcurrencyException because the Version in the database no longer matches what was
read.
try
{
product.MarkAsUpdated(userId);
await dbContext.SaveChangesAsync(ct);
}
catch (DbUpdateConcurrencyException)
{
// Reload and retry, or return a 409 to the caller.
}
SyncAuditBase
SyncAuditBase copies audit fields from one entity instance to another while bypassing the interceptor. It is
intended for internal synchronization scenarios (e.g., merging detached entity state) and should not be used in
normal update flows.
target.SyncAuditBase(source);
await dbContext.SaveChangesAsync(ct);
Setting IgnoreInterceptor = true via SyncAuditBase suppresses validation for all modified entities in that
SaveChanges call, so use it only when you are certain the audit state being applied is already correct.
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. 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 is compatible. 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. |
-
net10.0
- Microsoft.EntityFrameworkCore.Relational (>= 10.0.0)
-
net8.0
- Microsoft.EntityFrameworkCore.Relational (>= 8.0.0)
-
net9.0
- Microsoft.EntityFrameworkCore.Relational (>= 9.0.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Pandatech.EFCore.AuditBase:
| Package | Downloads |
|---|---|
|
Pandatech.SharedKernel.Postgres
PostgreSQL integration helpers for ASP.NET Core 10: DbContext registration with or without pooling and audit trail, migrations, health checks, snake_case naming, query locks, exception mapping, and bulk extensions. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 5.0.0 | 107 | 2/28/2026 |
| 4.0.1 | 127 | 1/26/2026 |
| 4.0.0 | 111 | 12/28/2025 |
| 3.0.10 | 459 | 8/7/2025 |
| 3.0.9 | 285 | 6/1/2025 |
| 3.0.8 | 269 | 4/7/2025 |
| 3.0.7 | 254 | 4/4/2025 |
| 3.0.6 | 230 | 4/4/2025 |
| 3.0.5 | 200 | 4/4/2025 |
| 3.0.4 | 219 | 4/4/2025 |
| 3.0.3 | 295 | 3/12/2025 |
| 3.0.2 | 230 | 2/28/2025 |
| 3.0.1 | 214 | 2/17/2025 |
| 3.0.0 | 324 | 12/1/2024 |
| 2.0.0 | 287 | 11/21/2024 |
| 1.2.1 | 234 | 11/11/2024 |
| 1.2.0 | 201 | 10/17/2024 |
| 1.1.0 | 221 | 7/12/2024 |
| 1.0.3 | 236 | 6/7/2024 |
| 1.0.2 | 242 | 5/31/2024 |
Multi-target net9.0/net10.0, removed Npgsql dependency, framework-pinned EF Core references