EfCore.AuditTrail
1.0.0
Sorry team, this package has one critical bug which I am currently working on. I will release another version soon.
dotnet add package EfCore.AuditTrail --version 1.0.0
NuGet\Install-Package EfCore.AuditTrail -Version 1.0.0
<PackageReference Include="EfCore.AuditTrail" Version="1.0.0" />
<PackageVersion Include="EfCore.AuditTrail" Version="1.0.0" />
<PackageReference Include="EfCore.AuditTrail" />
paket add EfCore.AuditTrail --version 1.0.0
#r "nuget: EfCore.AuditTrail, 1.0.0"
#:package EfCore.AuditTrail@1.0.0
#addin nuget:?package=EfCore.AuditTrail&version=1.0.0
#tool nuget:?package=EfCore.AuditTrail&version=1.0.0
EfCore.AuditTrail
Zero-config audit trails for EF Core. Automatically stamps every entity write with who did it, when, and what changed.
The Problem
You've written this code in every project:
- Manual
CreatedAt,UpdatedAttimestamp logic - Current user resolution from HttpContext
- SaveChanges overrides for audit logging
- Serialization of old/new values to a log table
The Solution
One line. Never write it again.
builder.Services.AddAuditTrail();
Install
dotnet add package EfCore.AuditTrail
Quick Start
1. Register the service
var builder = WebApplicationBuilder.CreateBuilder(args);
builder.Services.AddAuditTrail();
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(conn).AddAuditTrail()
);
var app = builder.Build();
2. Add audit fields to your entities
No base class required. Just declare the properties by convention:
public class Product
{
public Guid Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
// These are automatically stamped by the interceptor
public DateTime CreatedAt { get; set; }
public string? CreatedBy { get; set; }
public DateTime UpdatedAt { get; set; }
public string? UpdatedBy { get; set; }
}
3. Done
Every insert, update, or delete is now:
- Automatically timestamped
- User-attributed (resolved from HttpContext claims)
- Logged with full before/after snapshots in
AuditLogstable
var product = new Product { Name = "Widget", Price = 9.99m };
await db.Products.AddAsync(product);
await db.SaveChangesAsync();
// Audit log automatically created:
// {
// "Id": "abc-123",
// "TableName": "Products",
// "Operation": "Insert",
// "NewValues": {"Name":"Widget","Price":9.99},
// "OldValues": null,
// "UserId": "user-456",
// "UserName": "John Doe",
// "Timestamp": "2026-03-26T10:30:00Z"
// }
Querying Audit Logs
// All changes to Products
var logs = await db.AuditLogs()
.ForTable<Product>()
.OrderByNewest()
.ToListAsync();
// Changes by a specific user
var userChanges = await db.AuditLogs()
.ByUser("user-456")
.InRange(start, end)
.ToListAsync();
// Only update operations
var updates = await db.AuditLogs()
.WithOperation(AuditOperation.Update)
.ToListAsync();
Features
✅ Zero Configuration — No base classes, no interfaces, no overrides
✅ Convention-based — Just declare CreatedAt, UpdatedAt, CreatedBy, UpdatedBy
✅ Full Change History — Old values, new values, changed columns, timestamps
✅ User Attribution — Automatically resolves current user from HttpContext
✅ Multi-tenant Support — Include TenantId property for automatic isolation
✅ Soft Delete Support — Use [SoftDelete] attribute for logical deletes
✅ Queryable Audit Logs — LINQ-friendly helpers for filtering and sorting
✅ Multi-framework — Targets .NET 6, 7, 8, 9
Options
builder.Services.AddAuditTrail(options =>
{
options.SystemUserName = "system"; // Default user when none authenticated
options.EnableSoftDelete = true; // Support [SoftDelete] attribute
options.StoreIpAddress = false; // Store client IP in audit logs
options.AuditTableName = "AuditLogs"; // Custom table name
});
Custom User Provider
For non-web contexts (background jobs, CLI), implement ICurrentUserProvider:
public class CustomUserProvider : ICurrentUserProvider
{
public AuditUser? GetCurrentUser()
{
// Resolve from your own source
return new AuditUser("job-service", "Background Job");
}
}
builder.Services.AddScoped<ICurrentUserProvider, CustomUserProvider>();
Skip Properties
Exclude sensitive or high-cardinality properties:
public class User
{
public Guid Id { get; set; }
public string Email { get; set; }
[AuditIgnore]
public string PasswordHash { get; set; } // Not logged
}
Soft Delete
Mark entities for logical (soft) deletes:
[SoftDelete]
public class Document
{
public Guid Id { get; set; }
public string Title { get; set; }
public DateTime DeletedAt { get; set; }
public string? DeletedBy { get; set; }
}
// Deletes are logged, entity not physically removed
var doc = await db.Documents.FindAsync(id);
db.Documents.Remove(doc);
await db.SaveChangesAsync(); // Soft-deleted, tracked in audit log
Supported Databases
- ✅ SQL Server (2016+)
- ✅ PostgreSQL (10+)
- ✅ MySQL (5.7+)
- ✅ SQLite (3.0+)
How It Works
EfCore.AuditTrail uses EF Core's SaveChangesInterceptor:
- Before SaveChanges — Snapshots all pending changes (old/new values)
- Stamp Audit Fields — Sets CreatedAt/UpdatedAt/By by convention
- After SaveChanges — Writes
AuditEntryrows toAuditLogstable (now with PKs for inserts)
No reflection on every query, no runtime proxies—just interception at the save boundary.
Testing
dotnet test
The test suite includes:
- Insert/Update/Delete audit logging
- Timestamp convention stamping
- User provider integration
- Property snapshotting
- Query extension helpers
Contributing
We welcome contributions!
- Fork the repo
- Create a feature branch (
git checkout -b feature/my-feature) - Commit your changes (
git commit -am 'Add my feature') - Push to the branch (
git push origin feature/my-feature) - Open a Pull Request
License
EfCore.AuditTrail is licensed under the MIT License. See LICENSE for details.
Support
Built with ❤️ by Sourav Pal
| 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.AspNetCore.Http (>= 2.2.2)
- Microsoft.AspNetCore.Http.Abstractions (>= 2.2.0)
- Microsoft.EntityFrameworkCore (>= 8.0.25)
- Microsoft.EntityFrameworkCore.Relational (>= 8.0.25)
-
net8.0
- Microsoft.AspNetCore.Http (>= 2.2.2)
- Microsoft.AspNetCore.Http.Abstractions (>= 2.2.0)
- Microsoft.EntityFrameworkCore (>= 8.0.25)
- Microsoft.EntityFrameworkCore.Relational (>= 8.0.25)
-
net9.0
- Microsoft.AspNetCore.Http (>= 2.2.2)
- Microsoft.AspNetCore.Http.Abstractions (>= 2.2.0)
- Microsoft.EntityFrameworkCore (>= 8.0.25)
- Microsoft.EntityFrameworkCore.Relational (>= 8.0.25)
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 | |
|---|---|---|---|
| 1.0.0 | 174 | 3/26/2026 |