Headless.AuditLog.EntityFramework 0.4.15

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

Headless.AuditLog.EntityFramework

EF Core implementation of the audit log subsystem: change capture, persistent storage, and explicit event logging.

Problem Solved

Wires the audit log pipeline into EF Core's ChangeTracker so that entity mutations are captured and persisted atomically with the originating SaveChanges call — no separate commit, no data loss on rollback.

Key Features

  • EfAuditChangeCapture - Scans ChangeTracker before save; produces AuditLogEntryData per changed entity
  • EfAuditLogStore - Adds AuditLogEntry rows to the same DbContext so they commit in the same transaction
  • EfAuditLog<TContext> - Implements IAuditLog<TContext> for explicit event logging (reads, PII reveals, failures)
  • EfReadAuditLog<TContext> - Implements IReadAuditLog<TContext> for filtered read-back without leaking EF entities
  • AuditLogEntry - Single-table entity with JSON columns for OldValues, NewValues, and ChangedFields
  • ConfigureAuditLog() - ModelBuilder extension; supports custom table name, schema, and JSON column type
  • Soft-delete detection: automatically emits entity.soft_deleted / entity.restored actions when IsDeleted transitions
  • Suspend detection: emits entity.suspended / entity.unsuspended when IsSuspended transitions
  • EntityFilter and PropertyFilter results are cached after first evaluation for the capture service lifetime
  • Zero overhead when AuditLogOptions.IsEnabled is false

Installation

dotnet add package Headless.AuditLog.EntityFramework

Quick Start

DI setup

services.AddHeadlessAuditLog(o =>
{
    o.SensitiveDataStrategy = SensitiveDataStrategy.Redact;
});

services.AddHeadlessAuditLogEntity<AppDbContext>();

AddHeadlessAuditLogEntity requires AddHeadlessAuditLog (from Headless.AuditLog.Abstractions) to be called first for options registration, and AddHeadlessDbContext<T> for the DbContext registration.

DbContext setup

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    modelBuilder.ConfigureAuditLog();
}

Entity opt-in

public class Patient : AggregateRoot<Guid>, IAuditTracked
{
    public string Name { get; set; } = "";

    [AuditSensitive]
    public string NationalId { get; set; } = "";

    [AuditIgnore]
    public DateTime LastComputedAt { get; set; }
}

Explicit event logging

await auditLog.LogAsync(
    "pii.revealed",
    entityType: typeof(Patient).FullName,
    entityId: id.ToString()
);

Query audit entries

var entries = await readAuditLog.QueryAsync(
    action: "entity.updated",
    entityType: typeof(Patient).FullName,
    limit: 50,
    cancellationToken: cancellationToken
);

Configuration

PostgreSQL JSON columns

Pass jsonColumnType: "jsonb" to store OldValues, NewValues, and ChangedFields as native JSONB instead of serialized strings:

modelBuilder.ConfigureAuditLog(jsonColumnType: "jsonb");

Custom table and schema

modelBuilder.ConfigureAuditLog(tableName: "audit_entries", schema: "audit");

Sensitive data strategies

Strategy Behavior
Redact (default) Replaces value with "***"; property name still appears in ChangedFields
Exclude Omits the property entirely from OldValues, NewValues, and ChangedFields
Transform Passes value through AuditLogOptions.SensitiveValueTransformer (hash, mask, tokenize)

SensitiveValueTransformer must be configured whenever the effective strategy is Transform. Global misconfiguration fails options resolution; per-property [AuditSensitive(SensitiveDataStrategy.Transform)] without a transformer throws an OptionsValidationException during capture instead of silently redacting.

Per-property strategy override:

[AuditSensitive(SensitiveDataStrategy.Exclude)]
public string CreditCardToken { get; set; } = "";

Key Behaviors

  • Atomicity - Audit entries are added to the same DbContext and committed in the same transaction as entity changes
  • Zero overhead - When IsEnabled is false, CaptureChanges returns an empty list immediately
  • Disabled auditing signal - When IsEnabled is false, the first capture attempt logs a warning with remediation guidance
  • Soft-delete detection - Monitors IsDeleted and IsSuspended property transitions; emits semantic action names instead of generic entity.updated
  • Owned entities - Inherit auditability from their aggregate owner
  • Audit capture errors are non-fatal - If capturing a single entity fails, a warning is logged and the save continues without that entry
  • JSON round-trip shape - OldValues and NewValues deserialize non-string values as JsonElement; use GetDecimal(), GetBoolean(), and similar APIs when reading them back
  • Composite key encoding - Single-column EntityId values remain plain strings; composite keys are serialized as JSON string arrays such as ["tenant-a","order,42"]
  • Client metadata - IpAddress and UserAgent are persisted when explicitly supplied, but automatic EF change capture does not populate them

SQLite Limitation

The default entity configuration uses a composite primary key (CreatedAt, Id) for partition-readiness. SQLite does not support ValueGeneratedOnAdd (autoincrement) on composite keys. Consumers targeting SQLite must override the key configuration — for example, using a single-column PK on Id:

builder.HasKey(e => e.Id); // Override for SQLite

Migration Note

Composite-key EntityId values are now serialized as JSON arrays instead of comma-joined strings. Existing stored audit rows using the old comma-joined format remain unchanged; downstream parsers should handle both shapes during transition.

Dependencies

  • Headless.AuditLog.Abstractions
  • Headless.Orm.EntityFramework
  • Microsoft.EntityFrameworkCore

Side Effects

  • Registers IAuditChangeCapture as scoped (EfAuditChangeCapture)
  • Registers IAuditLogStore as scoped (EfAuditLogStore)
  • Registers IAuditLog<TContext> as scoped (EfAuditLog<TContext>)
  • Registers IReadAuditLog<TContext> as scoped (EfReadAuditLog<TContext>)
Product Compatible and additional computed target framework versions.
.NET 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
0.4.15 35 5/18/2026
0.4.14 37 5/18/2026