Auditing is a critical feature in any system that ensures accountability and transparency. Whether you are developing enterprise applications or smaller systems, tracking changes to data is essential for maintaining security, compliance, and data integrity. Entity Framework Core (EF Core) provides a flexible and extensible way to implement auditing by leveraging EF Core Interceptors. In this guide, we’ll explore how to implement auditing using EF Core Interceptors in a step-by-step manner.
Introduction to EF Core Interceptors
EF Core Interceptors are a powerful mechanism that allows developers to hook into the execution pipeline of EF Core operations. They enable pre-processing or post-processing logic during database transactions, such as CRUD operations, query execution, or command generation. This makes them ideal for tasks such as auditing, where you need to track changes to your data entities.
What is Auditing in EF Core?
In the context of EF Core, auditing refers to tracking the lifecycle of data within your application. It records actions such as:
- Inserts: When new records are added.
- Updates: When existing records are modified.
- Deletes: When records are removed from the database.
Each audit record typically contains metadata such as the user who made the changes, the timestamp of the action, and a snapshot of the data before and after the change.
Why Use EF Core Interceptors for Auditing?
Interceptors in EF Core allow us to intercept database operations in a central, reusable way. Here are the primary reasons to use EF Core Interceptors for auditing:
- Centralized Control: You can manage auditing in one place without adding logic to each entity.
- Performance Efficiency: It offers efficient ways to capture and store changes during the database lifecycle.
- Extensibility: Allows flexible tracking across different entity types and operations.
Setting up the Project
To get started, create a new project or use an existing one. You’ll need to install EF Core using the NuGet package manager.
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
Defining the Audit Entity
The next step is to define an entity that will store audit logs. Here’s an example of an AuditLog entity that will store information about changes made to your entities.
public class AuditLog
{
public int Id { get; set; }
public string EntityName { get; set; }
public string ActionType { get; set; } // Insert, Update, Delete
public string Changes { get; set; } // Store changes in JSON format
public string UserId { get; set; }
public DateTime Timestamp { get; set; }
}
Creating the Audit Log Table
Make sure you create a migration and update the database to apply the changes.
dotnet ef migrations add AddAuditLogTable
dotnet ef database update
This will create the corresponding AuditLogs table in your database.
Implementing EF Core Interceptors
Now that we have the audit log entity in place, let’s implement the interceptor that will capture the changes. EF Core provides several types of interceptors, but for auditing, we’ll focus on the SaveChangesInterceptor.
SaveChangesInterceptor
This interceptor allows us to hook into the save process (when SaveChanges is called). It provides methods to examine and manipulate the changes that EF Core is about to save.
public class AuditInterceptor : SaveChangesInterceptor
{
private readonly IHttpContextAccessor _httpContextAccessor;
public AuditInterceptor(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public override ValueTask<InterceptionResult<int>> SavingChangesAsync(
DbContextEventData eventData,
InterceptionResult<int> result,
CancellationToken cancellationToken = default)
{
var context = eventData.Context;
if (context == null) return base.SavingChangesAsync(eventData, result, cancellationToken);
var auditEntries = context.ChangeTracker.Entries()
.Where(e => e.State == EntityState.Added ||
e.State == EntityState.Modified ||
e.State == EntityState.Deleted)
.Select(e => CreateAuditEntry(e))
.ToList();
context.Set<AuditLog>().AddRange(auditEntries);
return base.SavingChangesAsync(eventData, result, cancellationToken);
}
private AuditLog CreateAuditEntry(EntityEntry entry)
{
var auditEntry = new AuditLog
{
EntityName = entry.Entity.GetType().Name,
ActionType = entry.State.ToString(),
Changes = JsonConvert.SerializeObject(entry.CurrentValues.Properties
.ToDictionary(p => p.Name, p => entry.CurrentValues[p.Name])),
Timestamp = DateTime.UtcNow,
UserId = _httpContextAccessor.HttpContext?.User?.Identity?.Name
};
return auditEntry;
}
}
CommandInterceptor
While the SaveChangesInterceptor is useful for tracking changes to entities, sometimes you might need to capture raw SQL commands executed by EF Core. This is where CommandInterceptor comes in.
Capturing Audit Data
The SavingChangesAsync method captures data changes before they are persisted in the database. Each change is examined, and an AuditLog entry is created.
Logging Insert, Update, and Delete Events
As shown in the interceptor implementation, you can handle insert, update, and delete events by examining the state of the entity in the change tracker. Depending on the entity’s state, the corresponding action type is logged in the AuditLog table.
Storing Audit Logs in the Database
Once the audit entries are created, they are added to the AuditLog DbSet and automatically saved during the same transaction as the data change.
Configuring the Interceptor in DbContext
To use the interceptor, you need to configure it in your DbContext. You can do this by overriding the AddInterceptors method in your OnConfiguring method.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.AddInterceptors(new AuditInterceptor(new HttpContextAccessor()));
}
Performance Considerations
Auditing can introduce some performance overhead, especially in high-transaction environments. To mitigate this:
- Log only essential data.
- Use efficient serialization methods (like JSON) for large datasets.
- Consider async logging mechanisms.
Best Practices for Auditing with EF Core
- Minimize Performance Impact: Log only necessary fields and changes.
- Use Async Operations: To avoid blocking operations, use asynchronous methods wherever possible.
- Centralize Auditing Logic: By using interceptors, ensure all database changes are logged centrally.
Conclusion
Implementing auditing with EF Core Interceptors is an efficient and scalable solution for tracking changes in your data. With SaveChangesInterceptor, you can capture entity changes, log them, and store audit records in a central location. This approach not only improves transparency but also ensures that your application complies with auditing requirements.
