Auditing Entity Changes
In this lesson, you will learn how to implement centralized auditing in EF Core to automatically track changes made to your business entities.
When building enterprise applications, simply storing the current state of data is often insufficient; you need an audit trail to track who changed what and when. While you could manually write audit logic in every service, the most robust approach is to leverage EF Core’s ChangeTracker inside your DbContext.
Centralizing Audit Logic
The most efficient place to intercept changes is by overriding the SaveChangesAsync method in your DbContext. This ensures that regardless of which part of your application modifies an entity, the audit record is created automatically.
Here is how you can capture the state of your entities:
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
var entries = ChangeTracker.Entries<IAuditable>()
.Where(e => e.State == EntityState.Added || e.State == EntityState.Modified);
foreach (var entry in entries)
{
entry.Entity.LastModified = DateTime.UtcNow;
entry.Entity.ModifiedBy = _currentUserService.UserId;
if (entry.State == EntityState.Added)
{
entry.Entity.Created = DateTime.UtcNow;
}
}
return await base.SaveChangesAsync(cancellationToken);
}
Why This Matters
By defining an IAuditable interface, you create a contract for your domain entities. This allows your DbContext to inspect any entity implementing that interface. The ChangeTracker provides a bird's-eye view of everything happening in the current session.
Instead of duplicating code throughout your business layer, you handle auditing at the infrastructure level. This centralizes your security and compliance requirements, ensuring that every time an entity is saved, its metadata remains accurate without the developer needing to remember to update timestamp properties manually.
If you need to store historical snapshots (the "before" and "after" values), you can expand this pattern to iterate through entry.Properties to compare OriginalValue versus CurrentValue and write those into a dedicated AuditLogs table.
Key Takeaway: By overriding SaveChangesAsync and utilizing the ChangeTracker, you can enforce consistent, automated auditing across your entire application without polluting your business logic.