AuditTrail.Core 1.3.0

dotnet add package AuditTrail.Core --version 1.3.0
                    
NuGet\Install-Package AuditTrail.Core -Version 1.3.0
                    
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="AuditTrail.Core" Version="1.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="AuditTrail.Core" Version="1.3.0" />
                    
Directory.Packages.props
<PackageReference Include="AuditTrail.Core" />
                    
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 AuditTrail.Core --version 1.3.0
                    
#r "nuget: AuditTrail.Core, 1.3.0"
                    
#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 AuditTrail.Core@1.3.0
                    
#: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=AuditTrail.Core&version=1.3.0
                    
Install as a Cake Addin
#tool nuget:?package=AuditTrail.Core&version=1.3.0
                    
Install as a Cake Tool

AuditTrail.Core

A plug-and-play .NET 8/9/10 library that automatically audits EF Core entity changes using a SaveChangesInterceptor. No SaveChanges overrides, no migrations, no schema management — just register and go.

Features

  • Captures table name, action (Added/Modified/Deleted), primary key(s), old values, new values, user, and timestamp
  • Creates and maintains the AuditLogs table on startup using raw DDL — no consumer migrations required
  • Compatible with SQL Server, PostgreSQL, SQLite, and any other EF Core relational provider
  • Startup connectivity check — logs a warning at startup if the audit database is unreachable
  • Pluggable user resolution via IAuditUserProvider — works in web apps, background services, and workers
  • [AuditIgnore] attribute (or fluent config) to exclude entities or properties from audit capture
  • [AuditMask] attribute (or fluent config) to redact sensitive fields with "[redacted]"
  • Configurable retry policy for transient audit write failures
  • Indexed on Timestamp and TableName for efficient querying

Installation

dotnet add package AuditTrail.Core --version 1.2.0

Setup

Program.cs

using AuditTrail.Core;

// 1. Register audit services — pass your provider directly.
//    The library does not reference any specific provider package.
builder.Services.AddAuditTrail(opts => opts.UseSqlServer(auditConnectionString));

// 2. Register your DbContext with the interceptor wired automatically.
builder.Services.AddDbContextWithAuditTrail<ApplicationDbContext>(
    opts => opts.UseSqlServer(appConnectionString));

var app = builder.Build();

// 3. Optional: mark the dependency visibly in your middleware pipeline.
//    Schema setup runs automatically via the hosted service regardless.
app.UseAuditTrail();

The library creates the AuditLogs table on first startup using provider-specific DDL (IF NOT EXISTS / CREATE TABLE IF NOT EXISTS). It also detects and upgrades the column type if you are upgrading from v1.0.x.

ApplicationDbContext.cs

No changes required — the interceptor is wired at the DI level:

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options) { }

    // ... your DbSets
}

That's it. Every SaveChanges / SaveChangesAsync call is intercepted and produces a row in AuditLogs.

Legacy pattern (still supported)

If you can't use AddDbContextWithAuditTrail, inject IServiceProvider and call UseAuditTrail on the options builder:

public class ApplicationDbContext : DbContext
{
    private readonly IServiceProvider _sp;

    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, IServiceProvider sp)
        : base(options) { _sp = sp; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder.UseAuditTrail(_sp);
}

Optional features

Excluding entities and properties

Apply [AuditIgnore] to skip an entire entity class or a single property:

[AuditIgnore]
public class SessionToken { ... }          // entire entity skipped

public class User
{
    [AuditIgnore]
    public string? InternalNote { get; set; }   // omitted from OldValues/NewValues JSON
}

For types you don't own, use the fluent API:

builder.Services.AddAuditTrail(
    opts => opts.UseSqlServer(connStr),
    features => features
        .Ignore<IdentityUserToken<string>>()
        .Ignore<User>(u => u.ConcurrencyStamp));

Masking sensitive fields

Apply [AuditMask] to replace a value with "[redacted]" in the captured JSON:

public class User
{
    [AuditMask]
    public string? PasswordHash { get; set; }

    [AuditMask]
    public string? SecurityStamp { get; set; }
}

Result: {"PasswordHash":"[redacted]","SecurityStamp":"[redacted]","Email":"user@example.com"}

Or configure via fluent API:

features.Mask<User>(u => u.PasswordHash);

Both [AuditIgnore] and [AuditMask] are inherited, so attributes on base-class properties are detected even when EF Core lazy-loading proxies are in use.

Retry policy

Re-attempt failed audit writes on subsequent saves (entries are held in memory between saves):

builder.Services.AddAuditTrail(
    opts => opts.UseSqlServer(connStr),
    features => features.RetryFailedEntries(maxRetries: 3));

If all retries are exhausted, the entries are discarded and logged at Error level via ILogger<AuditTrailInterceptor>.

Custom user resolver

The default resolver reads HttpContext.User.Identity.Name and falls back to "Anonymous". Override it for worker services, background jobs, or multi-tenant scenarios by registering your own IAuditUserProvider before AddAuditTrail:

// Register first so TryAddSingleton inside AddAuditTrail keeps yours.
builder.Services.AddSingleton<IAuditUserProvider, WorkerJobUserProvider>();
builder.Services.AddAuditTrail(opts => opts.UseSqlServer(connStr));

public class WorkerJobUserProvider : IAuditUserProvider
{
    private readonly ICurrentJobContext _job;
    public WorkerJobUserProvider(ICurrentJobContext job) => _job = job;
    public string GetCurrentUser() => _job.CurrentUser ?? "system";
}

Non-web consumers (console apps, worker services) should always register a custom provider — the default HTTP resolver returns "Anonymous" when there is no active HTTP context.

Querying audit logs

Inject AuditTrailDbContext wherever you need to query the audit history:

public class AuditService
{
    private readonly AuditTrailDbContext _auditDb;

    public AuditService(AuditTrailDbContext auditDb) => _auditDb = auditDb;

    public Task<List<AuditLog>> GetHistoryAsync(string tableName) =>
        _auditDb.AuditLogs
            .Where(a => a.TableName == tableName)
            .OrderByDescending(a => a.Timestamp)
            .ToListAsync();
}

AuditLog schema

Column Type Description
Id long Auto-generated primary key
TableName string Name of the affected table
Action string Added, Modified, or Deleted
PrimaryKey string JSON-serialized primary key value(s)
OldValues string JSON snapshot of values before the change (null for Added)
NewValues string JSON snapshot of values after the change (null for Deleted)
UserName string Resolved from IAuditUserProvider, or "Anonymous"
Timestamp DateTime UTC time of the change

Upgrading from 1.1.x

No breaking changes. New in 1.2.0:

  • AuditLogs schema is now created via raw DDL instead of EF migrations — the table is created for fresh installs and the column type is upgraded automatically on existing databases. No action required.
  • app.UseAuditTrail() extension added for IApplicationBuilder.
  • [AuditMask] and [AuditIgnore] now work correctly through EF Core lazy-loading proxy types.
  • Concurrent audit writes are serialized to prevent retry-queue race conditions under load.

Upgrading from 1.0.x

  • AuditLog.Id changed from int to long. Any code assigning Id to an int variable will produce a compile-time error — change those to long.
  • The library upgrades the column type automatically on startup (SQL Server and PostgreSQL). SQLite requires no change (INTEGER is already 64-bit).
  • AddAuditTrail gained an optional second parameter in 1.1.0. Existing single-argument call sites require no changes.
  • EF migrations are no longer used. Any previously applied migration entries in __EFMigrationsHistory are harmless — the raw DDL setup is idempotent.

Notes

  • Do not add DbSet<AuditLog> to your own DbContext — the library manages its own AuditTrailDbContext.
  • The AuditLogs table is created in the database configured in AddAuditTrail (which can be different from your application database).
  • AuditTrailDbContext can be injected directly to query audit logs.
  • Audit writes happen after the application transaction commits, in a separate AuditTrailDbContext. If the audit write fails, it is logged at Error level. Use RetryFailedEntries to tolerate transient failures.
  • The library does not reference any specific EF Core provider package. Add Microsoft.EntityFrameworkCore.SqlServer, Npgsql.EntityFrameworkCore.PostgreSQL, or whichever provider your project needs, and pass it via the AddAuditTrail delegate.

MIT License

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.3.0 50 6/5/2026
1.2.0 46 6/5/2026
1.0.2 47 6/5/2026
1.0.1 97 5/7/2026
1.0.0 92 5/7/2026