Learn how to tap into Entity Framework Core’s execution pipeline using interceptors, leverage the Decorator Pattern for clean cross-cutting concerns, and explore practical examples like SQL logging and auditing in action.
📚 Table of Contents
A Brief History of EF Core Interceptors
- What Are EF Core Interceptors?
- EF Core Interceptors and the Decorator Pattern
- Top Use Cases for EF Core Interceptors
- Example 1: Logging SQL Queries
- Example 2: Auditing SaveChanges
- Final Thoughts
A Brief History of EF Core Interceptors
EF Core interceptors were first introduced in EF Core 3.0 (September 2019), providing a powerful extensibility point for developers. For the first time, you could intercept low-level database operations like command execution, connections, and transactions.
Here’s how interceptor support has evolved since:
EF Core 5.0: Introduced SaveChangesInterceptor, enabling interception of entity lifecycle events.
EF Core 6.0+: Enhanced support for asynchronous interception and continued performance improvements.
What Are EF Core Interceptors?
Think of EF Core interceptors as middleware for database operations. They allow you to hook into the EF Core pipeline and execute logic before or after core actions like:
Executing SQL commands
✅Opening or closing database connections
✅Beginning or committing transactions
✅Saving changes to the database
With interceptors, you can elegantly introduce concerns such as:
✅Logging
✅Auditing
✅Validation
✅Metrics collection
all without touching your core business logic.
EF Core Interceptors and the Decorator Pattern
EF Core interceptors are a textbook example of the Decorator Pattern in action.
- The EF operation is the base object.
- Your interceptor wraps around it.
- You decorate behavior before or after the core operation—without altering the EF Core source or your app’s main flow.
This promotes clean separation of concerns, testability, and reusability across your application.
Top Use Cases for EF Core Interceptors
Here are some practical, real-world use cases:
✅ SQL Query Logging – Track raw SQL for debugging and performance tuning.
✅ Auditing – Automatically record who changed what and when.
✅ Soft Deletes – Intercept delete operations and convert them into updates.
✅ Command Modification – Dynamically alter SQL commands before execution.
✅ Multi-Tenant Filtering – Inject filters based on tenant context.
Example 1: Logging SQL Queries
Let’s start by logging every SQL command executed by EF Core.
Step 1: Create the Interceptor
using Microsoft.EntityFrameworkCore.Diagnostics;
using System.Data.Common;
using System.Diagnostics;
public class SqlLoggingInterceptor : DbCommandInterceptor
{
public override InterceptionResult<DbDataReader> ReaderExecuting(
DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result)
{
Debug.WriteLine($"SQL Executed: {command.CommandText}");
return base.ReaderExecuting(command, eventData, result);
}
}
Output:
SQL Executed: SELECT * FROM [Users] WHERE [IsActive] = 1
Step 2: Register the Interceptor
public class AppDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer("YourConnectionString")
.AddInterceptors(new SqlLoggingInterceptor());
}
}
Example 2: Auditing SaveChanges
Want to automatically track who modified entities and when? Let’s implement that.
Step 1: Define an Auditable Base Class
public abstract class AuditableEntity
{
public DateTime ModifiedAt { get; set; }
public string ModifiedBy { get; set; }
}
Step 2: Create the SaveChangesInterceptor
public class AuditInterceptor : SaveChangesInterceptor
{
private readonly string _currentUser;
public AuditInterceptor(string currentUser)
{
_currentUser = currentUser;
}
public override int SavingChanges(DbContextEventData eventData, int result)
{
var context = eventData.Context;
if (context == null) return base.SavingChanges(eventData, result);
var entries = context.ChangeTracker
.Entries<AuditableEntity>()
.Where(e => e.State == EntityState.Modified);
foreach (var entry in entries)
{
entry.Entity.ModifiedAt = DateTime.UtcNow;
entry.Entity.ModifiedBy = _currentUser;
}
return base.SavingChanges(eventData, result);
}
}
Now, every modified entity will have ModifiedAt and ModifiedBy set automatically.
Final Thoughts
EF Core interceptors are a powerful yet underused feature in the .NET ecosystem. They enable developers to introduce cross-cutting concerns without bloating business logic or infrastructure layers.
Because they follow the Decorator Pattern, interceptors promote maintainable, testable, and reusable architecture.
Whether you’re logging, auditing, **modifying **SQL on the fly, or enforcing security policies—EF Core interceptors are a clean and scalable solution worth mastering.
🔗 References
EF Core Documentation – Interceptors