NashTech Blog

Practical Business Use Cases of EF Core Interceptors

Table of Contents

What Are EF Core Interceptors?

EF Core interceptors are hooks that let you run custom logic before or after EF executes database operations. Instead of sprinkling logic across services, you can enforce business rules centrally in your data access layer.

Purpose

  • Centralize cross-cutting logic
  • Enforce consistency across entities
  • Extend EF Core behavior without cluttering application code

Pros and Cons

✅ Pros

  • Keeps business rules in one place
  • Reduces repetitive code
  • Works for both SaveChanges and SQL commands
  • Increases maintainability in large systems

❌ Cons

  • Can make rules less visible to developers
  • Debugging is harder if you forget about them
  • Potential performance overhead if overused

Common Business Use Cases (Summary)

  1. Auto-Fill Properties → Automatically update fields like UpdatedDate
  2. Entity Validation → Enforce business rules globally (e.g., Age > 18)
  3. Audit Logging → Track inserts, updates, and deletes
  4. Soft Deletes → Mark rows as deleted instead of physically removing them
  5. SQL Command Logging & Profiling → Inspect and monitor generated SQL

Detailed Examples

1. Auto-Fill Properties (UpdatedDate)

Ensures audit fields like UpdatedDate are always correct, without manual handling.


public class UpdateDateInterceptor : SaveChangesInterceptor
{
    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);

        foreach (var entry in context.ChangeTracker.Entries())
        {
            if (entry.State == EntityState.Modified &&
                entry.Properties.Any(p => p.Metadata.Name == "UpdatedDate"))
            {
                entry.Property("UpdatedDate").CurrentValue = DateTime.UtcNow;
            }
        }

        return base.SavingChangesAsync(eventData, result, cancellationToken);
    }
}

2. Entity Validation (Age > 18)

Enforces rules across all entities without extra service logic.


public class ValidationInterceptor : SaveChangesInterceptor
{
    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);

        foreach (var entry in context.ChangeTracker.Entries<Person>())
        {
            if ((entry.State == EntityState.Added || entry.State == EntityState.Modified) &&
                entry.Entity.Age < 18)
            {
                throw new InvalidOperationException("Age must be greater than 18.");
            }
        }

        return base.SavingChangesAsync(eventData, result, cancellationToken);
    }
}

public class Person
{
    public int Id { get; set; }
    public int Age { get; set; }
}

3. Audit Logging

Tracks data changes for compliance and debugging.


public class AuditLogInterceptor : SaveChangesInterceptor
{
    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);

        foreach (var entry in context.ChangeTracker.Entries())
        {
            if (entry.State == EntityState.Added || 
                entry.State == EntityState.Modified || 
                entry.State == EntityState.Deleted)
            {
                Console.WriteLine($"AUDIT: {entry.Entity.GetType().Name} - {entry.State}");
            }
        }

        return base.SavingChangesAsync(eventData, result, cancellationToken);
    }
}

4. Soft Deletes

Prevents permanent data loss by flipping an IsDeleted flag.


public class SoftDeleteInterceptor : SaveChangesInterceptor
{
    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);

        foreach (var entry in context.ChangeTracker.Entries<ISoftDeletable>())
        {
            if (entry.State == EntityState.Deleted)
            {
                entry.State = EntityState.Modified;
                entry.Entity.IsDeleted = true;
            }
        }

        return base.SavingChangesAsync(eventData, result, cancellationToken);
    }
}

public interface ISoftDeletable
{
    bool IsDeleted { get; set; }
}

5. SQL Command Logging & Profiling

Captures raw SQL queries for performance monitoring.


public class CommandLoggingInterceptor : DbCommandInterceptor
{
    public override InterceptionResult<int> NonQueryExecuting(
        DbCommand command, 
        CommandEventData eventData, 
        InterceptionResult<int> result)
    {
        Console.WriteLine($"SQL: {command.CommandText}");
        return base.NonQueryExecuting(command, eventData, result);
    }
}

Registering Interceptors

Add them to your DbContext:


protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .AddInterceptors(new UpdateDateInterceptor())
        .AddInterceptors(new ValidationInterceptor())
        .AddInterceptors(new AuditLogInterceptor())
        .AddInterceptors(new SoftDeleteInterceptor())
        .AddInterceptors(new CommandLoggingInterceptor());
}

Conclusion

EF Core interceptors are powerful for:

  • Auto-filling properties like timestamps
  • Validating business rules
  • Auditing changes
  • Soft deletion strategies
  • Query logging and profiling

They keep your application clean by centralizing cross-cutting logic, but should be used carefully to avoid hidden complexity.

Picture of Huỳnh Minh Tú

Huỳnh Minh Tú

Senior Software Engineer

Leave a Comment

Your email address will not be published. Required fields are marked *

Suggested Article

Scroll to Top