Pandatech.EFCore.Audit 1.2.1

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

// Install Pandatech.EFCore.Audit as a Cake Tool
#tool nuget:?package=Pandatech.EFCore.Audit&version=1.2.1                

Pandatech.EFCore.Audit

Pandatech.EFCore.Audit is a powerful and configurable library designed to collect audit trail data from the EF Core DbContext change tracker. It is built with scalability and professional-grade extensibility in mind.

Features

  • Scalable & Configurable: Tailor the behavior to meet your project's needs.
  • Composite Key Handling: Returns concatenated composite keys in a single property using _ as the delimiter.
  • Property Transformation: Customize tracked properties (e.g., rename, transform, or ignore).

Limitations

  • Not atomic: Being event-based, there is a risk of losing audit data in edge cases.
  • Does not work with untracked operations like AsNoTracking, ExecuteUpdate, ExecuteDelete, etc. For such scenarios, use the new manual bulk audit feature described below.

Installation

Install the NuGet package:

dotnet add package Pandatech.EFCore.Audit

Integration

To integrate Pandatech.EFCore.Audit into your project, follow these steps:

1. Configure DbContext

Set up your DbContext to include your entities:

public class PostgresContext(DbContextOptions options) : DbContext(options)
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }
}

2. Configure Entities

Entities can be set up for auditing using custom configurations. Below are examples:

Blog Entity
public class Blog
{
    public int Id { get; set; }
    public required string Title { get; set; }
    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
    public BlogType BlogType { get; set; }
    public required byte[] EncryptedKey { get; set; }
}

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

public enum Permission
{
    AdminPermission,
    UserPermission
}
Post Entity
public class Post
{
    public int Id { get; set; }
    public int BlogId { get; set; }
    public required string Title { get; set; }
    public required string Content { get; set; }
    public Blog Blog { get; set; } = null!;
}

public class PostAuditConfiguration : AuditTrailConfigurator<Post>
{
    public PostAuditConfiguration()
    {
        SetServiceName("BlogService");
        RuleFor(s => s.Content).Ignore();
        RuleFor(s => s.Title).Rename("TotallyNewTitle");
    }
}
Configuration Details
  • SetServiceName: Specifies a custom service name that will be returned during the audit trail event. This can be useful for identifying the origin of the change.
  • SetReadPermission: Assigns a predefined permission level included in the event, enabling better control over who can access the audit information as row-level security.
  • WriteAuditTrailOnEvents: Defines the specific events (Create, Update, Delete) on which an entity should be tracked. If this option is not configured, all events for the entity will be tracked by default.
  • Exclusion of Configuration: If an entity should not be audited, its configuration should be omitted entirely. Entities without configuration will not be tracked.
  • Transform: Allows you to apply a custom function to modify the value of a property before it is recorded in the audit trail. For example, this can be used to encrypt/decrypt or format data.
  • Ignore: Skips tracking of the specified property within the entity. Useful for sensitive or irrelevant data.
  • Rename: Changes the property name in the audit trail output. This is useful for aligning property names with business-specific terminology or conventions.

3. Register DbContext in Program.cs

Register your DbContext and add the audit trail interceptors:

public static WebApplicationBuilder AddPostgresContext<TContext>(this WebApplicationBuilder builder,
    string connectionString)
    where TContext : DbContext
{
    builder.Services.AddDbContextPool<TContext>((sp, options) =>
    {
        options
            .UseNpgsql(connectionString)
            .AddAuditTrailInterceptors(sp);
    });

    return builder;
}

4. Set Up the Audit Trail Consumer

To handle audit trail events, create a consumer class that inherits from IAuditTrailConsumer and implements the ConsumeAuditTrailAsync method. Implement this method to process audit trail events according to your application's requirements — for example, logging the events, sending them to an external service, or storing them in a database.

Here is an example implementation that serializes the event data to JSON and writes it to the console:

public class AuditTrailConsumer : IAuditTrailConsumer
{
   public Task ConsumeAuditTrailAsync(AuditTrailEventData auditTrailEventData)
   {
      Console.WriteLine(JsonSerializer.Serialize(auditTrailEventData));
      return Task.CompletedTask;
   }
}

5. Program.cs Registration and Configuration

Below is a simplified example of how your Program.cs file might look:

var builder = WebApplication.CreateBuilder(args);

// Register audit trail configurations with the consumer class before adding the DbContext
builder.Services.AddAuditTrail<AuditTrailConsumer>(typeof(Program).Assembly);

// Register DbContext with audit trail interceptors
builder.AddPostgresContext<PostgresContext>(
    "Server=localhost;Port=5432;Database=audit_test;User Id=test;Password=test;Pooling=true;");

var app = builder.Build();

app.Run();

Note: The AddAuditTrail method registers the audit trail configurations and HttpContextAccessor so it should be always before the AddDbContext method. In case of using AddDbContext before registration make sure to register the HttpContextAccessor manually by using builder.Services.AddHttpContextAccessor() method in Program.cs.

6. Audit Trail Event Data

The audit trail event data is represented by the following classes:

public record AuditTrailEventData(List<AuditTrailEventEntity> Entities);

public record AuditTrailEventEntity(
    EntityEntry Entry,
    string? ServiceName,
    AuditActionType ActionType,
    string EntityName,
    object? ReadPermission,
    string PrimaryKeyValue,
    Dictionary<string, object?> TrackedProperties);
  • AuditTrailEventData: Contains a list of AuditTrailEventEntity objects.
  • AuditTrailEventEntity: Represents an audited entity with its associated data.
    • Entry: The EntityEntry object containing the entity data from DbContext.
    • ServiceName: The name of the service where the change originated. Configured manually using SetServiceName.
    • ActionType: The type of action performed (Create, Update, Delete).
    • EntityName: The name of the entity.
    • ReadPermission: The assigned permission level for accessing this audit trail. Configured manually using SetReadPermission.
    • PrimaryKeyValue: The primary key value(s) of the entity.
    • TrackedProperties: A dictionary containing the tracked properties and their values.

7. Manual Bulk Audit

If you perform operations outside EF Core’s change tracker (e.g. AsNoTracking, ExecuteUpdate(), raw SQL queries, external APIs, etc.), you can still create audit events by manually specifying:

  • AuditActionType (Create, Update, Delete)
  • Primary key value(s)
  • The dictionary of changed properties relevant to the operation

This is exposed via a single IAuditTrailPublisher.BulkAuditAsync method (or similar) that accepts a list of manual audit entries. Each entry contains:

public record ManualAuditEntry(
    Type EntityType,              // The CLR type you're auditing (e.g. Blog, Post)
    AuditActionType Action,       // Create, Update, or Delete
    List<AuditEntryDetail> ChangedItems // One or more records to track
);

public record AuditEntryDetail(
    List<string> PrimaryKeyIds,          // e.g. ["10"] or ["10","20"] for composite PK
    Dictionary<string, object?> ChangedProperties // developer-supplied property data
);
Usage Example
public async Task CreatePublish()
{
  var posts = new List<Post>();

  var blog = new Blog
  {
     Id = 2,
     Title = "null",
     CreatedAt = DateTime.UtcNow,
     BlogType = BlogType.Personal,
     EncryptedKey = [1, 2, 3]
  };

  for (var i = 0; i < 10; i++)
  {
     var post = new Post
     {
        Title = $"Post {i}",
        Content = $"This is post {i}",
        Blog = blog
     };

     posts.Add(post);
  }

  var auditEntries = new List<ManualAuditEntry>
  {
     new(
        typeof(Blog),
        AuditActionType.Create,
        [
           new AuditEntryDetail(
              PrimaryKeyIds: [blog.Id.ToString()],

              ChangedProperties: new Dictionary<string, object?>
              {
                 [nameof(blog.Id)] = blog.Id,
                 [nameof(blog.Title)] = blog.Title,
                 [nameof(blog.CreatedAt)] = blog.CreatedAt,
                 [nameof(blog.BlogType)] = blog.BlogType,
                 [nameof(blog.EncryptedKey)] = blog.EncryptedKey
              }
           )
        ]
     )
  };

  var postDetails = new List<AuditEntryDetail>();
  foreach (var p in posts)
  {
     postDetails.Add(
        new AuditEntryDetail(
           PrimaryKeyIds: [p.Id.ToString()],
           ChangedProperties: new Dictionary<string, object?>
           {
              [nameof(p.Title)] = p.Title,
              [nameof(p.Content)] = p.Content,
              [nameof(p.BlogId)] = p.BlogId
           }
        )
     );
  }

  auditEntries.Add(
     new ManualAuditEntry(
        EntityType: typeof(Post),
        Action: AuditActionType.Create,
        ChangedItems: postDetails
     )
  );

  await auditPublisher.BulkAuditAsync(auditEntries); // IAuditTrailPublisher.BulkAuditAsync
}

When you call BulkAuditAsync, the library will apply any configured transformations (ignore, rename, transform) and publish the resulting audit trail event.

Notes

  • Partial Property Tracking: For Update actions, TrackedProperties only includes properties that have been modified.
  • Event Handling: The provided Console.WriteLine in the demo is a placeholder. You are responsible for implementing your own event handling logic.
  • Database Compatibility: Compatible with PostgreSQL and other relational databases supported by EF Core.
  • **Compatible with .Net 9 +

License

This project is licensed under the MIT License.

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible. 
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

Pandatech.SharedKernel.Postgres simplifies PostgreSQL integration in ASP.NET Core applications by providing utilities for Entity Framework Core setup, health checks, and other enhancements.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.2.1 4 12/26/2024
1.2.0 9 12/26/2024
1.1.4 89 12/5/2024
1.1.3 92 12/3/2024
1.1.2 82 12/3/2024
1.1.1 88 12/3/2024
1.1.0 96 12/3/2024
1.0.0 84 12/2/2024

Cancellation token support added