Pandatech.EFCore.Audit 3.0.2

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

Pandatech.EFCore.Audit

Automatic audit trail tracking for Entity Framework Core 8+ with configurable property transformations, composite key support, and manual bulk auditing.

Installation

dotnet add package Pandatech.EFCore.Audit

Quick Start

1. Configure Entities for Auditing

public class Blog
{
    public int Id { get; set; }
    public string Title { get; set; }
    public DateTime CreatedAt { get; set; }
    public byte[] EncryptedKey { get; set; }
}

public class BlogAuditConfiguration : AuditTrailConfigurator<Blog>
{
    public BlogAuditConfiguration()
    {
        SetReadPermission(Permission.UserPermission);
        WriteAuditTrailOnEvents(AuditActionType.Create, AuditActionType.Update, AuditActionType.Delete);
        RuleFor(x => x.EncryptedKey).Transform(Convert.ToBase64String);
    }
}

2. Create Audit Consumer

public class AuditTrailConsumer : IAuditTrailConsumer
{
    public Task ConsumeAuditTrailAsync(AuditTrailEventData eventData, CancellationToken ct = default)
    {
        // Log to database, send to message queue, etc.
        Console.WriteLine(JsonSerializer.Serialize(eventData));
        return Task.CompletedTask;
    }
}

3. Register Services

var builder = WebApplication.CreateBuilder(args);

// Register audit trail BEFORE DbContext
builder.AddAuditTrail<AuditTrailConsumer>(typeof(Program).Assembly);

// Register DbContext with audit interceptors
builder.Services.AddDbContext<AppDbContext>((sp, options) =>
{
    options.UseNpgsql(connectionString)
           .AddAuditTrailInterceptors(sp);
});

Configuration API

Entity Configuration

public class PostAuditConfiguration : AuditTrailConfigurator<Post>
{
    public PostAuditConfiguration()
    {
        // Set service name for this entity
        SetServiceName("BlogService");
        
        // Track only specific events (default: all events)
        WriteAuditTrailOnEvents(AuditActionType.Create, AuditActionType.Update);
        
        // Set read permission for row-level security
        SetReadPermission(Permission.AdminOnly);
        
        // Property transformations
        RuleFor(x => x.Content).Ignore();                    // Don't track
        RuleFor(x => x.Title).Rename("PostTitle");          // Rename in audit
        RuleFor(x => x.Price).Transform(x => Math.Round(x, 2)); // Transform value
    }
}

Property Configuration Methods

Method Description Example
Ignore() Skip tracking this property RuleFor(x => x.Password).Ignore()
Rename(string) Change property name in audit trail RuleFor(x => x.Email).Rename("UserEmail")
Transform<TOutput>(Func) Transform value before auditing RuleFor(x => x.Data).Transform(Encrypt)

Entity Configuration Methods

Method Description
SetServiceName(string) Identify the originating service
SetReadPermission(object) Set permission level for row-level security
WriteAuditTrailOnEvents(params AuditActionType[]) Track only specific events (Create, Update, Delete)

Audit Event Data

public record AuditTrailEventData(List<AuditTrailEventEntity> Entities);

public record AuditTrailEventEntity(
    EntityEntry? EntityEntry,      // EF Core entity entry (null for manual audits)
    string? ServiceName,            // Service identifier
    AuditActionType ActionType,    // Create, Update, or Delete
    string Name,                    // Entity type name
    object? ReadPermission,        // Permission level
    string PrimaryKeyValue,        // Composite keys joined with '_'
    Dictionary<string, object?> TrackedProperty
);

Example audit event:

{
  "entities": [{
    "serviceName": "BlogService",
    "actionType": "Update",
    "name": "Blog",
    "readPermission": 1,
    "primaryKeyValue": "123",
    "trackedProperty": {
      "title": "New Title",
      "encryptedKey": "AQID"
    }
  }]
}

Manual Bulk Auditing

For operations outside EF Core's change tracker (raw SQL, ExecuteUpdate, AsNoTracking, external APIs):

public class MyService
{
    private readonly IAuditTrailPublisher _publisher;
    
    public async Task BulkCreatePosts()
    {
        // Your untracked operations...
        await db.Database.ExecuteSqlRawAsync("INSERT INTO Posts ...");
        
        // Manually create audit entries
        var auditEntries = new List<ManualAuditEntry>
        {
            new(
                EntityType: typeof(Post),
                Action: AuditActionType.Create,
                ChangedItems: new List<AuditEntryDetail>
                {
                    new(
                        PrimaryKeyIds: ["1"],
                        ChangedProperties: new Dictionary<string, object?>
                        {
                            ["Title"] = "My Post",
                            ["Content"] = "Post content",
                            ["BlogId"] = 42
                        }
                    ),
                    new(
                        PrimaryKeyIds: ["2"],
                        ChangedProperties: new Dictionary<string, object?>
                        {
                            ["Title"] = "Another Post",
                            ["Content"] = "More content",
                            ["BlogId"] = 42
                        }
                    )
                }
            )
        };
        
        await _publisher.BulkAuditAsync(auditEntries);
    }
}

Features

Automatic change tracking - Hooks into EF Core's change tracker
Composite key support - Concatenates composite keys with _ delimiter
Property transformations - Ignore, rename, or transform tracked properties
Transaction support - Publishes audit events after transaction commit
Manual bulk auditing - Track untracked operations like raw SQL
Configurable permissions - Row-level security support via SetReadPermission
Service identification - Track which service made the change
Partial tracking - For updates, only changed properties are tracked

Limitations

⚠️ Not atomic - Event-based architecture means audit data could be lost in edge cases
⚠️ No untracked operations - AsNoTracking, ExecuteUpdate, ExecuteDelete are not automatically audited (use manual bulk audit)

Advanced Usage

Composite Keys

public class OrderItem
{
    public int OrderId { get; set; }
    public int ProductId { get; set; }
    // ...
}

Audit event: primaryKeyValue: "123_456" (OrderId_ProductId)

Custom Transformations

public class UserAuditConfiguration : AuditTrailConfigurator<User>
{
    public UserAuditConfiguration()
    {
        RuleFor(x => x.Email).Transform(x => MaskEmail(x));
        RuleFor(x => x.Salary).Transform(x => x * 0.01m); // Store in cents
        RuleFor(x => x.CreatedAt).Transform(x => x.ToString("o")); // ISO 8601
    }
    
    private static string MaskEmail(string email)
    {
        var parts = email.Split('@');
        return parts.Length == 2 
            ? $"{parts[0][0]}***@{parts[1]}" 
            : email;
    }
}

Selective Event Tracking

public class SensitiveDataConfig : AuditTrailConfigurator<SensitiveData>
{
    public SensitiveDataConfig()
    {
        // Only track deletions, not creates or updates
        WriteAuditTrailOnEvents(AuditActionType.Delete);
    }
}

Event Consumer Examples

Database Consumer

public class DatabaseAuditConsumer : IAuditTrailConsumer
{
    private readonly AuditDbContext _auditDb;
    
    public async Task ConsumeAuditTrailAsync(AuditTrailEventData eventData, CancellationToken ct)
    {
        var auditRecords = eventData.Entities.Select(e => new AuditRecord
        {
            EntityName = e.Name,
            Action = e.ActionType.ToString(),
            PrimaryKey = e.PrimaryKeyValue,
            Changes = JsonSerializer.Serialize(e.TrackedProperty),
            Timestamp = DateTime.UtcNow,
            ServiceName = e.ServiceName,
            Permission = e.ReadPermission?.ToString()
        });
        
        _auditDb.AuditRecords.AddRange(auditRecords);
        await _auditDb.SaveChangesAsync(ct);
    }
}

Message Queue Consumer

public class RabbitMqAuditConsumer : IAuditTrailConsumer
{
    private readonly IMessagePublisher _publisher;
    
    public async Task ConsumeAuditTrailAsync(AuditTrailEventData eventData, CancellationToken ct)
    {
        var message = new AuditMessage
        {
            Timestamp = DateTime.UtcNow,
            Events = eventData.Entities
        };
        
        await _publisher.PublishAsync("audit-trail", message, ct);
    }
}

Registration Order

⚠️ Important: AddAuditTrail must be called before AddDbContext:

// ✅ Correct order
builder.AddAuditTrail<AuditConsumer>(typeof(Program).Assembly);
builder.Services.AddDbContext<AppDbContext>(...);

// ❌ Wrong order - will not work
builder.Services.AddDbContext<AppDbContext>(...);
builder.AddAuditTrail<AuditConsumer>(typeof(Program).Assembly);

If you must register DbContext first, manually add HttpContextAccessor:

builder.Services.AddHttpContextAccessor();
builder.Services.AddDbContext<AppDbContext>(...);
builder.AddAuditTrail<AuditConsumer>(typeof(Program).Assembly);

Supported Databases

  • PostgreSQL
  • SQL Server
  • MySQL
  • SQLite
  • Any database supported by EF Core Relational

License

MIT

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 (1)

Showing the top 1 NuGet packages that depend on Pandatech.EFCore.Audit:

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
3.0.2 41 6/3/2026
3.0.1 70 6/1/2026
3.0.0 232 2/28/2026
2.0.1 147 1/26/2026
2.0.0 162 12/28/2025
1.2.5 469 8/7/2025
1.2.4 315 6/1/2025
1.2.3 383 3/12/2025
1.2.2 261 2/17/2025
1.2.1 272 12/26/2024
1.2.0 230 12/26/2024
1.1.4 238 12/5/2024
1.1.3 239 12/3/2024
1.1.2 215 12/3/2024
1.1.1 218 12/3/2024
1.1.0 221 12/3/2024
1.0.0 207 12/2/2024

Multi-target net8.0/9.0/10.0, Update Audit Tracking Service Logic