burtonrodman.EntityFrameworkCore.Auditing 3.0.4

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

EntityFrameworkCore.Auditing

A base class and utilities to implement auditing with SQL Server temporal tables.

Getting Started

Add NuGet Packages

  • Microsoft.EntityFrameworkCore.SqlServer
  • Microsoft.EntityFrameworkCore.Design
  • burtonrodman.EntityFrameworkCore.Auditing

Implement ICurrentUserAccessor or use DelegateCurrentUserAccessor

  1. Create a class that implements the ICurrentUserAccessor interface and returns the current user's identifier (whatever that means in your domain). This will often be the Name property of the current ClaimsPrincipal, but may vary in your domain.
public class CurrentUserAccessor : ICurrentUserAccessor
{
    public string GetUserName() => "current.user@domain.com";
}
  1. Register it in the DI container. Example:
services.AddScoped<ICurrentUserAccessor, CurrentUserAccessor>();

Alternatively you may provide an instance of DelegateCurrentUserAccessor that takes a Func<string>, or Func<IServiceProvider, string> that retrieves the user name.

services.AddScoped<ICurrentUserAccessor>(new DelegateCurrentUserAccessor(() => "current.user@domain.com"));

OR

services.AddScoped<ICurrentUserAccessor>(serviceProvider => new DelegateCurrentUserAccessor(sp => {
    var context = sp.GetRequiredService<IHttpContextAccessor>();
    return context.HttpContext.Identity?.Name;
}, serviceProvider));

Setup your DbContext

  1. Add a using for burtonrodman.EntityFrameworkCore.Auditing to your DbContext file or global usings.

Usings.cs:

global using burtonrodman.EntityFrameworkCore.Auditing;
  1. Change the base type of your DbContext to AuditableDbContext and add or modify the constructor to take the instance of ICurrentUserAccessor and pass it to the base constructor.
public class SampleDbContext : AuditableDbContext
{
    public SampleDbContext(
        DbContextOptions<SampleDbContext> dbContextOptions,
        ICurrentUserAccessor currentUserAccessor)
        : base(dbContextOptions, currentUserAccessor)
    { }
  1. On any entity types that you want to be auditable, set the base class to AuditableEntityBase.
public class BlogPost : AuditableEntityBase
  1. OPTIONAL: determine if the current run-time context is talking to SQL Server:

for testing scenarios where you may be using an in-memory or sqlite provider, the shadow properties provided by the SQL Server provider will not be available. We have the shouldAddShadowProperties parameter that adds them so your queries using the PeriodStart and PeriodEnd columns do not break during testing.

  1. override OnModelCreating:
    override protected void OnModelCreating(ModelBuilder modelBuilder)
    {

      modelBuilder.ApplyConfigurationsFromAssembly(typeof(SampleDbContext).Assembly);

      ConfigureTemporalTables(modelBuilder,
        shouldAddShadowProperties: !this.Database.IsSqlServer());

    }
  1. OPTIONAL: override the Period column names:
   public SampleDbContext(
       DbContextOptions<SampleDbContext> dbContextOptions,
       ICurrentUserAccessor currentUserAccessor)
       : base(dbContextOptions, currentUserAccessor)
   { 
     // OPTIONAL:  override the default Period column names
     this.PeriodStart = "SysStartTime";
     this.PeriodEnd = "SysEndTime";
   }

Create A Migration

Now that your DbContext and entities are configured, you may generate a new migration that will apply System-versioning and also add the ModifiedBy column to your table(s).

dotnet ef migrations add AddAuditing
dotnet ef database update

How Does It Work?

SQL Server has a feature called "Temporal Tables" or "System-versioned Tables". When enabled on a table, 2 columns are added to the existing table -- their names may vary, but I have chosen PeriodStart and PeriodEnd. In addition, another table with the same schema and the suffix History is created. From then on, any insert the PeriodStart is populated with the current server time; any update inserts a new row with PeriodStart as the current server time and moves the old version of the row to the History table, with PeriodEnd also set; any delete removes the row from the current table and inserts into the History table with the PeriodEnd set.

In order to create a full audit log (including when AND who), this library adds the ModifiedBy field. SaveChanges[Async] is overridden and updates the ModifiedBy column. For deleted rows, the state is changed to Modified and the ModifiedBy field is updated before finally deleting the row. This gives full auditing of inserts, updates and deletes.

Troubleshooting

  • PROBLEM: I need to access the PeriodStart and PeriodEnd column names statically for things like AutoMapper Profiles.
    • SOLUTION: add constants to your DbContext class, and pass to ConfigureTemporalTables
  public partial class MyDbContext : AuditableDbContext
  {
    // provide static values for PeriodStart and PeriodEnd for mapping Profile
    public const string PeriodStartColumnName = "SysStartTime";
    public const string PeriodEndColumnName = "SysEndTime";

Use in constructor:

      // OPTIONAL:  override the default Period column names
      this.PeriodStart = PeriodStartColumnName;
      this.PeriodEnd = PeriodEndColumnName;

Contributing

I welcome Pull Requests for any improvement or bug fixes. Please open an Issue for discussion if you plan on adding any features, so that we can collaborate on design. For bug reports, a Pull Request with a failing unit test is ideal.

Thanks!

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  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 was computed.  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
3.0.4 1,269 9/27/2025
3.0.3 168 9/27/2025
3.0.2 7,336 12/7/2024
3.0.1 2,282 8/16/2024
3.0.0 242 8/16/2024
2.0.0 538 8/2/2024
1.0.3 1,261 5/1/2024
1.0.2 14,201 11/15/2023
1.0.1 1,243 10/2/2023
1.0.0 233 9/29/2023
0.1.3 306 7/1/2023
0.1.2 264 6/17/2023
0.1.1 271 6/17/2023