Skip to content

拦截器(Interceptors) ​

EF Core 拦截器是一种强大的横切关注点实现机制,允许你在 EF Core 操作执行前后注入自定义逻辑。本章将深入探讨拦截器的各种应用场景,包括审计日志、软删除、性能监控、多租户隔离等。

目录 ​


1. 拦截器基础 ​

1.1 什么是拦截器? ​

拦截器(Interceptor)是 EF Core 提供的一种扩展机制,允许你在数据库操作执行前后插入自定义逻辑,而无需修改业务代码。

拦截器 vs 其他扩展点:

csharp
// 对比各种扩展方式:

// 1. SaveChanges 重写 - 只能在保存时介入
public override int SaveChanges()
{
    // ❌ 只能拦截保存操作
    return base.SaveChanges();
}

// 2. 值转换器 - 只能转换特定类型
entity.Property(e => e.Status)
    .HasConversion<string>();  // ❌ 只能转换值

// 3. 拦截器 - 可以在任何操作前后介入
public class MyInterceptor : SaveChangesInterceptor
{
    public override async ValueTask<InterceptionResult<int>> SavingChangesAsync(...)
    {
        // ✅ 可以拦截查询、保存、命令等所有操作
        return await base.SavingChangesAsync(...);
    }
}

1.2 拦截器类型 ​

EF Core 支持多种拦截器接口:

csharp
// 1. 保存变更拦截器
public interface ISaveChangesInterceptor : IInterceptor
{
    ValueTask<InterceptionResult<int>> SavingChangesAsync(...);
    ValueTask<int> SavedChangesAsync(...);
}

// 2. 命令拦截器
public interface IDbCommandInterceptor : IInterceptor
{
    ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(...);
    ValueTask<DbDataReader> ReaderExecutedAsync(...);
    ValueTask<InterceptionResult<object>> ScalarExecutingAsync(...);
    ValueTask<object> ScalarExecutedAsync(...);
    ValueTask<InterceptionResult<int>> NonQueryExecutingAsync(...);
    ValueTask<int> NonQueryExecutedAsync(...);
}

// 3. 连接拦截器
public interface IDbConnectionInterceptor : IInterceptor
{
    Task ConnectionOpeningAsync(...);
    Task ConnectionOpenedAsync(...);
    Task ConnectionClosingAsync(...);
    Task ConnectionClosedAsync(...);
}

// 4. 事务拦截器
public interface IDbTransactionInterceptor : IInterceptor
{
    ValueTask<InterceptionResult<DbTransaction>> TransactionStartingAsync(...);
    ValueTask<DbTransaction> TransactionStartedAsync(...);
    ValueTask<InterceptionResult> TransactionCommittingAsync(...);
    ValueTask TransactionCommittedAsync(...);
    ValueTask<InterceptionResult> TransactionRollingBackAsync(...);
    ValueTask TransactionRolledBackAsync(...);
}

// 5. 迁移拦截器
public interface IMigrationsInterceptor : IInterceptor
{
    ValueTask<InterceptionResult> MigratingAsync(...);
    ValueTask MigratedAsync(...);
}

1.3 注册拦截器 ​

.NET 8/9/10 Minimal API 风格:

csharp
var builder = WebApplication.CreateBuilder(args);

// 方法1: 直接注册
builder.Services.AddDbContext<AppDbContext>(options =>
{
    options.UseSqlServer(builder.Configuration.GetConnectionString("Default"));
    options.AddInterceptors(new AuditInterceptor());
    options.AddInterceptors(new SoftDeleteInterceptor());
});

// 方法2: 从依赖注入容器注册
builder.Services.AddSingleton<AuditInterceptor>();
builder.Services.AddSingleton<SoftDeleteInterceptor>();

builder.Services.AddDbContext<AppDbContext>((sp, options) =>
{
    options.UseSqlServer(builder.Configuration.GetConnectionString("Default"));
    
    var auditInterceptor = sp.GetRequiredService<AuditInterceptor>();
    var softDeleteInterceptor = sp.GetRequiredService<SoftDeleteInterceptor>();
    
    options.AddInterceptors(auditInterceptor, softDeleteInterceptor);
});

// 方法3: 使用配置批量注册
builder.Services.AddDbContext<AppDbContext>((sp, options) =>
{
    options.UseSqlServer(builder.Configuration.GetConnectionString("Default"));
    options.AddInterceptors(sp.GetServices<IInterceptor>());
});

1.4 拦截器执行顺序 ​

csharp
// 注册多个拦截器时,按注册顺序执行
options.AddInterceptors(
    new LoggingInterceptor(),      // 1. 首先执行
    new AuditInterceptor(),        // 2. 然后执行
    new RetryInterceptor()         // 3. 最后执行
);

// 对于异步操作,执行链如下:
// Before: Logging -> Audit -> Retry -> [实际DB操作]
// After:  Retry -> Audit -> Logging <- 反向执行

2. 审计日志拦截器 ​

2.1 完整的审计日志实现 ​

定义审计记录实体:

csharp
public class AuditRecord
{
    public long Id { get; set; }
    
    public string EntityName { get; set; }
    public string EntityId { get; set; }
    
    public string Action { get; set; }  // Insert, Update, Delete
    
    public string OldValues { get; set; }  // JSON
    public string NewValues { get; set; }  // JSON
    public string ChangedProperties { get; set; }  // JSON
    
    public string UserId { get; set; }
    public string IpAddress { get; set; }
    public string UserAgent { get; set; }
    
    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}

// 配置
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<AuditRecord>(entity =>
    {
        entity.HasKey(e => e.Id);
        
        entity.Property(e => e.EntityName).IsRequired().HasMaxLength(200);
        entity.Property(e => e.Action).IsRequired().HasMaxLength(50);
        entity.Property(e => e.UserId).HasMaxLength(100);
        
        // 添加索引以提高查询性能
        entity.HasIndex(e => e.EntityName);
        entity.HasIndex(e => e.CreatedAt);
        entity.HasIndex(e => e.UserId);
    });
}

实现审计拦截器:

csharp
public class AuditInterceptor : SaveChangesInterceptor
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly List<AuditRecord> _auditRecords = new();
    
    public AuditInterceptor(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }
    
    public override async ValueTask<InterceptionResult<int>> SavingChangesAsync(
        DbContextEventData eventData,
        InterceptionResult<int> result,
        CancellationToken cancellationToken = default)
    {
        var context = eventData.Context;
        if (context == null) return result;
        
        // 遍历所有被跟踪的实体
        var entries = context.ChangeTracker.Entries()
            .Where(e => e.State is EntityState.Added or EntityState.Modified or EntityState.Deleted);
        
        foreach (var entry in entries)
        {
            // 跳过审计记录本身,避免无限循环
            if (entry.Entity is AuditRecord) continue;
            
            var auditRecord = CreateAuditRecord(entry);
            _auditRecords.Add(auditRecord);
        }
        
        return await base.SavingChangesAsync(eventData, result, cancellationToken);
    }
    
    public override async ValueTask<int> SavedChangesAsync(
        SaveChangesCompletedEventData eventData,
        int result,
        CancellationToken cancellationToken = default)
    {
        var context = eventData.Context;
        
        // 在保存成功后,将审计记录添加到数据库
        if (context != null && _auditRecords.Any())
        {
            context.Set<AuditRecord>().AddRange(_auditRecords);
            await context.SaveChangesAsync(cancellationToken);
            
            _auditRecords.Clear();
        }
        
        return await base.SavedChangesAsync(eventData, result, cancellationToken);
    }
    
    private AuditRecord CreateAuditRecord(EntityEntry entry)
    {
        var audit = new AuditRecord
        {
            EntityName = entry.Entity.GetType().Name,
            EntityId = GetEntityId(entry),
            Action = entry.State.ToString(),
            UserId = GetCurrentUserId(),
            IpAddress = GetClientIpAddress(),
            UserAgent = GetUserAgent(),
            CreatedAt = DateTime.UtcNow
        };
        
        // 记录变更的属性
        if (entry.State == EntityState.Modified)
        {
            var changedProps = entry.Properties
                .Where(p => p.IsModified)
                .Select(p => new
                {
                    PropertyName = p.Metadata.Name,
                    OldValue = p.OriginalValue,
                    NewValue = p.CurrentValue
                })
                .ToList();
            
            audit.ChangedProperties = JsonSerializer.Serialize(changedProps);
            audit.OldValues = JsonSerializer.Serialize(
                changedProps.ToDictionary(p => p.PropertyName, p => p.OldValue));
            audit.NewValues = JsonSerializer.Serialize(
                changedProps.ToDictionary(p => p.PropertyName, p => p.NewValue));
        }
        else if (entry.State == EntityState.Added)
        {
            audit.NewValues = JsonSerializer.Serialize(
                entry.Properties.ToDictionary(p => p.Metadata.Name, p => p.CurrentValue));
        }
        else if (entry.State == EntityState.Deleted)
        {
            audit.OldValues = JsonSerializer.Serialize(
                entry.Properties.ToDictionary(p => p.Metadata.Name, p => p.OriginalValue));
        }
        
        return audit;
    }
    
    private string GetEntityId(EntityEntry entry)
    {
        // 尝试获取主键值
        var keyProperties = entry.Metadata.FindPrimaryKey()?.Properties;
        if (keyProperties != null && keyProperties.Any())
        {
            var keyValues = keyProperties.Select(kp => entry.Property(kp.Name).CurrentValue);
            return string.Join(",", keyValues);
        }
        
        return "Unknown";
    }
    
    private string GetCurrentUserId()
    {
        var user = _httpContextAccessor.HttpContext?.User;
        return user?.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? "System";
    }
    
    private string GetClientIpAddress()
    {
        return _httpContextAccessor.HttpContext?.Connection.RemoteIpAddress?.ToString() 
               ?? "Unknown";
    }
    
    private string GetUserAgent()
    {
        return _httpContextAccessor.HttpContext?.Request.Headers["User-Agent"].FirstOrDefault() 
               ?? "Unknown";
    }
}

使用审计日志:

csharp
// 查询审计历史
var auditHistory = await context.AuditRecords
    .Where(a => a.EntityName == "Product" && a.EntityId == "123")
    .OrderByDescending(a => a.CreatedAt)
    .ToListAsync();

// 查看某个用户的操作
var userActions = await context.AuditRecords
    .Where(a => a.UserId == "user123" && a.CreatedAt > DateTime.UtcNow.AddDays(-7))
    .GroupBy(a => a.Action)
    .Select(g => new { Action = g.Key, Count = g.Count() })
    .ToListAsync();

// 恢复被删除的数据
var deletedRecord = await context.AuditRecords
    .Where(a => a.EntityName == "Order" && a.Action == "Delete")
    .OrderByDescending(a => a.CreatedAt)
    .FirstOrDefaultAsync();

if (deletedRecord != null)
{
    var oldData = JsonSerializer.Deserialize<Order>(deletedRecord.OldValues);
    context.Orders.Add(oldData);
    await context.SaveChangesAsync();
}

2.2 轻量级审计 - 仅记录关键信息 ​

如果不需要完整的审计记录,可以使用简化版本:

csharp
public class SimpleAuditInterceptor : SaveChangesInterceptor
{
    private readonly ILogger<SimpleAuditInterceptor> _logger;
    
    public SimpleAuditInterceptor(ILogger<SimpleAuditInterceptor> logger)
    {
        _logger = logger;
    }
    
    public override async ValueTask<int> SavedChangesAsync(
        SaveChangesCompletedEventData eventData,
        int result,
        CancellationToken cancellationToken = default)
    {
        var context = eventData.Context;
        
        if (context != null)
        {
            var changes = context.ChangeTracker.Entries()
                .Where(e => e.State is EntityState.Added or EntityState.Modified or EntityState.Deleted)
                .Select(e => new
                {
                    Entity = e.Entity.GetType().Name,
                    State = e.State,
                    Timestamp = DateTime.UtcNow
                })
                .ToList();
            
            if (changes.Any())
            {
                _logger.LogInformation("Changes saved: {@Changes}", changes);
            }
        }
        
        return await base.SavedChangesAsync(eventData, result, cancellationToken);
    }
}

3. 软删除拦截器 ​

3.1 软删除基础 ​

定义软删除接口和基类:

csharp
// 标记可软删除的实体
public interface ISoftDeletable
{
    bool IsDeleted { get; set; }
    DateTime? DeletedAt { get; set; }
}

// 基类实现
public abstract class SoftDeletableEntity : ISoftDeletable
{
    public bool IsDeleted { get; set; }
    public DateTime? DeletedAt { get; set; }
}

// 应用示例
public class Product : SoftDeletableEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

public class Order : SoftDeletableEntity
{
    public int Id { get; set; }
    public DateTime OrderDate { get; set; }
}

实现软删除拦截器:

csharp
public class SoftDeleteInterceptor : SaveChangesInterceptor
{
    public override async ValueTask<InterceptionResult<int>> SavingChangesAsync(
        DbContextEventData eventData,
        InterceptionResult<int> result,
        CancellationToken cancellationToken = default)
    {
        var context = eventData.Context;
        if (context == null) return result;
        
        // 查找所有被标记为删除的实体
        var deletedEntries = context.ChangeTracker.Entries()
            .Where(e => e.State == EntityState.Deleted);
        
        foreach (var entry in deletedEntries)
        {
            // 如果实体实现了 ISoftDeletable,转换为软删除
            if (entry.Entity is ISoftDeletable softDeletable)
            {
                // 改为更新操作
                entry.State = EntityState.Unchanged;
                
                // 设置软删除字段
                softDeletable.IsDeleted = true;
                softDeletable.DeletedAt = DateTime.UtcNow;
                
                // 标记属性为已修改,确保保存到数据库
                entry.Property(nameof(ISoftDeletable.IsDeleted)).IsModified = true;
                entry.Property(nameof(ISoftDeletable.DeletedAt)).IsModified = true;
            }
        }
        
        return await base.SavingChangesAsync(eventData, result, cancellationToken);
    }
}

3.2 全局查询过滤器自动应用 ​

在 DbContext 中配置全局过滤器:

csharp
public class AppDbContext : DbContext
{
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        
        // 为所有实现 ISoftDeletable 的实体添加全局过滤器
        var softDeletableTypes = modelBuilder.Model.GetEntityTypes()
            .Where(t => typeof(ISoftDeletable).IsAssignableFrom(t.ClrType));
        
        foreach (var entityType in softDeletableTypes)
        {
            // 动态创建过滤器: e => !e.IsDeleted
            var parameter = Expression.Parameter(entityType.ClrType, "e");
            var property = Expression.Property(parameter, nameof(ISoftDeletable.IsDeleted));
            var notExpression = Expression.Not(property);
            var lambda = Expression.Lambda(notExpression, parameter);
            
            modelBuilder.Entity(entityType.ClrType).HasQueryFilter(lambda);
        }
    }
}

使用效果:

csharp
// 自动过滤已删除的数据
var products = await context.Products.ToListAsync();
// SQL: SELECT * FROM Products WHERE IsDeleted = 0

// 显式包含已删除的数据
var allProducts = await context.Products
    .IgnoreQueryFilters()  // 忽略全局过滤器
    .ToListAsync();
// SQL: SELECT * FROM Products

// 查询已删除的数据
var deletedProducts = await context.Products
    .IgnoreQueryFilters()
    .Where(p => p.IsDeleted)
    .ToListAsync();

3.3 级联软删除 ​

当删除父实体时,自动软删除相关子实体:

csharp
public class CascadeSoftDeleteInterceptor : SaveChangesInterceptor
{
    public override async ValueTask<InterceptionResult<int>> SavingChangesAsync(
        DbContextEventData eventData,
        InterceptionResult<int> result,
        CancellationToken cancellationToken = default)
    {
        var context = eventData.Context;
        if (context == null) return result;
        
        var deletedEntries = context.ChangeTracker.Entries()
            .Where(e => e.State == EntityState.Deleted)
            .ToList();
        
        foreach (var entry in deletedEntries)
        {
            if (entry.Entity is ISoftDeletable softDeletable)
            {
                // 查找相关的子实体
                var navigations = entry.Navigations
                    .Where(n => n.IsCollection && n.TargetEntityType.ClrType.GetInterface(nameof(ISoftDeletable)) != null);
                
                foreach (var navigation in navigations)
                {
                    var relatedEntities = navigation.CurrentValue as IEnumerable<ISoftDeletable>;
                    
                    if (relatedEntities != null)
                    {
                        foreach (var relatedEntity in relatedEntities.Where(e => !e.IsDeleted))
                        {
                            // 软删除子实体
                            relatedEntity.IsDeleted = true;
                            relatedEntity.DeletedAt = DateTime.UtcNow;
                        }
                    }
                }
                
                // 转换父实体为软删除
                entry.State = EntityState.Unchanged;
                softDeletable.IsDeleted = true;
                softDeletable.DeletedAt = DateTime.UtcNow;
            }
        }
        
        return await base.SavingChangesAsync(eventData, result, cancellationToken);
    }
}

4. 性能监控拦截器 ​

4.1 SQL 执行监控 ​

csharp
public class PerformanceMonitoringInterceptor : DbCommandInterceptor
{
    private readonly ILogger<PerformanceMonitoringInterceptor> _logger;
    private readonly IMetricsCollector _metrics;
    private readonly Stopwatch _stopwatch = new();
    
    public PerformanceMonitoringInterceptor(
        ILogger<PerformanceMonitoringInterceptor> logger,
        IMetricsCollector metrics)
    {
        _logger = logger;
        _metrics = metrics;
    }
    
    public override async ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(
        DbCommand command,
        CommandEventData eventData,
        InterceptionResult<DbDataReader> result,
        CancellationToken cancellationToken = default)
    {
        _stopwatch.Restart();
        return await base.ReaderExecutingAsync(command, eventData, result, cancellationToken);
    }
    
    public override async ValueTask<DbDataReader> ReaderExecutedAsync(
        DbCommand command,
        CommandExecutedEventData eventData,
        DbDataReader result,
        CancellationToken cancellationToken = default)
    {
        _stopwatch.Stop();
        
        var duration = _stopwatch.ElapsedMilliseconds;
        var sql = TruncateSql(command.CommandText, 500);
        
        // 记录指标
        _metrics.RecordMetric("ef_query_duration_ms", duration);
        _metrics.RecordMetric("ef_query_count", 1);
        
        // 慢查询告警
        if (duration > 1000)  // 超过1秒
        {
            _logger.LogWarning("Slow query detected ({Duration}ms):\n{SQL}", 
                duration, sql);
        }
        
        // 记录详细日志
        _logger.LogDebug("Query executed in {Duration}ms:\n{SQL}\nParameters: {@Parameters}",
            duration,
            sql,
            command.Parameters.Cast<DbParameter>()
                .ToDictionary(p => p.ParameterName, p => p.Value));
        
        return await base.ReaderExecutedAsync(command, eventData, result, cancellationToken);
    }
    
    public override async ValueTask<InterceptionResult<object>> ScalarExecutingAsync(
        DbCommand command,
        CommandEventData eventData,
        InterceptionResult<object> result,
        CancellationToken cancellationToken = default)
    {
        _stopwatch.Restart();
        return await base.ScalarExecutingAsync(command, eventData, result, cancellationToken);
    }
    
    public override async ValueTask<object> ScalarExecutedAsync(
        DbCommand command,
        CommandExecutedEventData eventData,
        object result,
        CancellationToken cancellationToken = default)
    {
        _stopwatch.Stop();
        
        var duration = _stopwatch.ElapsedMilliseconds;
        _metrics.RecordMetric("ef_scalar_query_duration_ms", duration);
        
        if (duration > 1000)
        {
            _logger.LogWarning("Slow scalar query ({Duration}ms): {SQL}", duration, command.CommandText);
        }
        
        return await base.ScalarExecutedAsync(command, eventData, result, cancellationToken);
    }
    
    public override async ValueTask<InterceptionResult<int>> NonQueryExecutingAsync(
        DbCommand command,
        CommandEventData eventData,
        InterceptionResult<int> result,
        CancellationToken cancellationToken = default)
    {
        _stopwatch.Restart();
        return await base.NonQueryExecutingAsync(command, eventData, result, cancellationToken);
    }
    
    public override async ValueTask<int> NonQueryExecutedAsync(
        DbCommand command,
        CommandExecutedEventData eventData,
        int result,
        CancellationToken cancellationToken = default)
    {
        _stopwatch.Stop();
        
        var duration = _stopwatch.ElapsedMilliseconds;
        _metrics.RecordMetric("ef_non_query_duration_ms", duration);
        
        if (duration > 1000)
        {
            _logger.LogWarning("Slow non-query ({Duration}ms): {SQL}", duration, command.CommandText);
        }
        
        return await base.NonQueryExecutedAsync(command, eventData, result, cancellationToken);
    }
    
    private string TruncateSql(string sql, int maxLength)
    {
        if (string.IsNullOrEmpty(sql) || sql.Length <= maxLength)
            return sql;
        
        return sql.Substring(0, maxLength) + "...";
    }
}

4.2 Prometheus 集成 ​

csharp
public class PrometheusMetricsCollector : IMetricsCollector
{
    private static readonly Histogram QueryDurationHistogram = Metrics
        .CreateHistogram("ef_core_query_duration_seconds", "EF Core query duration", 
            new HistogramConfiguration
            {
                LabelNames = new[] { "query_type" },
                Buckets = Histogram.ExponentialBuckets(0.01, 2, 10)
            });
    
    private static readonly Counter QueryCountCounter = Metrics
        .CreateCounter("ef_core_query_total", "Total number of EF Core queries");
    
    public void RecordMetric(string name, double value)
    {
        switch (name)
        {
            case "ef_query_duration_ms":
                QueryDurationHistogram.WithLabels("query").Observe(value / 1000.0);
                break;
            case "ef_query_count":
                QueryCountCounter.Inc();
                break;
        }
    }
}

// 在 Program.cs 中暴露指标端点
app.MapMetrics();  // Prometheus 指标端点

5. 多租户隔离拦截器 ​

5.1 基于租户ID的数据隔离 ​

定义多租户接口:

csharp
public interface IMultiTenant
{
    string TenantId { get; set; }
}

public class Product : IMultiTenant
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string TenantId { get; set; }  // 租户ID
}

实现租户隔离拦截器:

csharp
public class MultiTenantInterceptor : SaveChangesInterceptor
{
    private readonly ITenantProvider _tenantProvider;
    
    public MultiTenantInterceptor(ITenantProvider tenantProvider)
    {
        _tenantProvider = tenantProvider;
    }
    
    public override async ValueTask<InterceptionResult<int>> SavingChangesAsync(
        DbContextEventData eventData,
        InterceptionResult<int> result,
        CancellationToken cancellationToken = default)
    {
        var context = eventData.Context;
        if (context == null) return result;
        
        var currentTenantId = _tenantProvider.GetCurrentTenantId();
        
        if (string.IsNullOrEmpty(currentTenantId))
        {
            throw new InvalidOperationException("Tenant ID is not set");
        }
        
        // 自动设置新实体的租户ID
        var addedEntries = context.ChangeTracker.Entries()
            .Where(e => e.State == EntityState.Added);
        
        foreach (var entry in addedEntries)
        {
            if (entry.Entity is IMultiTenant multiTenant)
            {
                multiTenant.TenantId = currentTenantId;
            }
        }
        
        return await base.SavingChangesAsync(eventData, result, cancellationToken);
    }
}

// 租户提供者
public interface ITenantProvider
{
    string GetCurrentTenantId();
}

public class HttpContextTenantProvider : ITenantProvider
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    
    public HttpContextTenantProvider(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }
    
    public string GetCurrentTenantId()
    {
        // 从请求头获取租户ID
        var tenantId = _httpContextAccessor.HttpContext?.Request.Headers["X-Tenant-Id"].FirstOrDefault();
        
        if (string.IsNullOrEmpty(tenantId))
        {
            // 从子域名提取租户ID
            var host = _httpContextAccessor.HttpContext?.Request.Host.Host;
            if (!string.IsNullOrEmpty(host))
            {
                tenantId = host.Split('.')[0];  // tenant1.example.com -> tenant1
            }
        }
        
        return tenantId ?? "default";
    }
}

配置全局查询过滤器:

csharp
public class MultiTenantDbContext : DbContext
{
    private readonly ITenantProvider _tenantProvider;
    
    public MultiTenantDbContext(DbContextOptions options, ITenantProvider tenantProvider)
        : base(options)
    {
        _tenantProvider = tenantProvider;
    }
    
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        
        // 为所有实现 IMultiTenant 的实体添加过滤器
        var multiTenantTypes = modelBuilder.Model.GetEntityTypes()
            .Where(t => typeof(IMultiTenant).IsAssignableFrom(t.ClrType));
        
        foreach (var entityType in multiTenantTypes)
        {
            var parameter = Expression.Parameter(entityType.ClrType, "e");
            var property = Expression.Property(parameter, nameof(IMultiTenant.TenantId));
            var tenantId = Expression.Constant(_tenantProvider.GetCurrentTenantId());
            var equalExpression = Expression.Equal(property, tenantId);
            var lambda = Expression.Lambda(equalExpression, parameter);
            
            modelBuilder.Entity(entityType.ClrType).HasQueryFilter(lambda);
        }
    }
}

使用效果:

csharp
// 自动过滤当前租户的数据
var products = await context.Products.ToListAsync();
// SQL: SELECT * FROM Products WHERE TenantId = 'tenant1'

// 自动设置新实体的租户ID
context.Products.Add(new Product { Name = "Product A" });
await context.SaveChangesAsync();
// 插入的 Product 自动设置 TenantId = 'tenant1'

6. 领域事件发布拦截器 ​

6.1 领域事件模式 ​

定义领域事件:

csharp
// 标记接口
public interface IDomainEvent
{
    DateTime OccurredOn { get; }
}

// 订单创建事件
public record OrderCreatedEvent : IDomainEvent
{
    public int OrderId { get; }
    public string CustomerId { get; }
    public decimal TotalAmount { get; }
    public DateTime OccurredOn { get; } = DateTime.UtcNow;
    
    public OrderCreatedEvent(int orderId, string customerId, decimal totalAmount)
    {
        OrderId = orderId;
        CustomerId = customerId;
        TotalAmount = totalAmount;
    }
}

// 库存扣减事件
public record InventoryDeductedEvent : IDomainEvent
{
    public int ProductId { get; }
    public int Quantity { get; }
    public DateTime OccurredOn { get; } = DateTime.UtcNow;
    
    public InventoryDeductedEvent(int productId, int quantity)
    {
        ProductId = productId;
        Quantity = quantity;
    }
}

// 实体中收集事件
public abstract class EntityBase
{
    private readonly List<IDomainEvent> _domainEvents = new();
    
    public IReadOnlyCollection<IDomainEvent> DomainEvents => _domainEvents.AsReadOnly();
    
    protected void AddDomainEvent(IDomainEvent domainEvent)
    {
        _domainEvents.Add(domainEvent);
    }
    
    public void ClearDomainEvents()
    {
        _domainEvents.Clear();
    }
}

public class Order : EntityBase
{
    public int Id { get; set; }
    public string CustomerId { get; set; }
    public decimal TotalAmount { get; set; }
    
    public void CreateOrder(string customerId, List<OrderItem> items)
    {
        CustomerId = customerId;
        TotalAmount = items.Sum(i => i.Quantity * i.UnitPrice);
        
        // 添加领域事件
        AddDomainEvent(new OrderCreatedEvent(Id, customerId, TotalAmount));
    }
}

实现事件发布拦截器:

csharp
public class DomainEventPublisherInterceptor : SaveChangesInterceptor
{
    private readonly IMediator _mediator;  // MediatR
    
    public DomainEventPublisherInterceptor(IMediator mediator)
    {
        _mediator = mediator;
    }
    
    public override async ValueTask<int> SavedChangesAsync(
        SaveChangesCompletedEventData eventData,
        int result,
        CancellationToken cancellationToken = default)
    {
        var context = eventData.Context;
        
        if (context != null)
        {
            // 获取所有包含领域事件的实体
            var entitiesWithEvents = context.ChangeTracker.Entries<EntityBase>()
                .Select(e => e.Entity)
                .Where(e => e.DomainEvents.Any())
                .ToList();
            
            // 发布所有领域事件
            foreach (var entity in entitiesWithEvents)
            {
                var events = entity.DomainEvents.ToList();
                entity.ClearDomainEvents();  // 清除事件,避免重复发布
                
                foreach (var domainEvent in events)
                {
                    // 发布到 MediatR
                    await _mediator.Publish(domainEvent, cancellationToken);
                }
            }
        }
        
        return await base.SavedChangesAsync(eventData, result, cancellationToken);
    }
}

事件处理器:

csharp
// 订单创建事件处理器
public class OrderCreatedEventHandler : INotificationHandler<OrderCreatedEvent>
{
    private readonly IEmailService _emailService;
    private readonly ILogger<OrderCreatedEventHandler> _logger;
    
    public OrderCreatedEventHandler(IEmailService emailService, ILogger<OrderCreatedEventHandler> logger)
    {
        _emailService = emailService;
        _logger = logger;
    }
    
    public async Task Handle(OrderCreatedEvent notification, CancellationToken cancellationToken)
    {
        _logger.LogInformation("Order created: {OrderId}", notification.OrderId);
        
        // 发送确认邮件
        await _emailService.SendOrderConfirmationAsync(notification.CustomerId, notification.OrderId);
        
        // 触发其他业务逻辑
        // - 更新客户统计
        // - 发送短信通知
        // - 推送到数据分析系统
    }
}

// 库存扣减事件处理器
public class InventoryDeductedEventHandler : INotificationHandler<InventoryDeductedEvent>
{
    private readonly ICacheService _cacheService;
    
    public InventoryDeductedEventHandler(ICacheService cacheService)
    {
        _cacheService = cacheService;
    }
    
    public async Task Handle(InventoryDeductedEvent notification, CancellationToken cancellationToken)
    {
        // 清除产品缓存
        await _cacheService.InvalidateAsync($"product:{notification.ProductId}");
        
        // 更新库存预警
        if (notification.Quantity < 10)
        {
            await SendLowStockAlert(notification.ProductId);
        }
    }
}

7. 连接弹性与重试 ​

7.1 transient 故障处理 ​

csharp
public class ResilientConnectionInterceptor : DbConnectionInterceptor
{
    private readonly ILogger<ResilientConnectionInterceptor> _logger;
    private readonly IResiliencePipeline _resiliencePipeline;
    
    public ResilientConnectionInterceptor(
        ILogger<ResilientConnectionInterceptor> logger,
        IResiliencePipeline resiliencePipeline)
    {
        _logger = logger;
        _resiliencePipeline = resiliencePipeline;
    }
    
    public override async Task ConnectionOpeningAsync(
        DbConnection connection,
        ConnectionEventData eventData,
        InterceptionResult eventResult,
        CancellationToken cancellationToken = default)
    {
        try
        {
            // 使用重试策略打开连接
            await _resiliencePipeline.ExecuteAsync(async ct =>
            {
                await connection.OpenAsync(ct);
            }, cancellationToken);
            
            _logger.LogInformation("Database connection opened successfully");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to open database connection after retries");
            throw;
        }
    }
    
    public override async Task ConnectionClosingAsync(
        DbConnection connection,
        ConnectionEventData eventData,
        InterceptionResult eventResult,
        CancellationToken cancellationToken = default)
    {
        try
        {
            await connection.CloseAsync();
            _logger.LogDebug("Database connection closed");
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Error closing database connection");
        }
    }
}

// 配置 Polly 重试策略
builder.Services.AddResiliencePipeline("ef-core", pipeline =>
{
    pipeline.AddRetry(new RetryStrategyOptions
    {
        MaxRetryAttempts = 3,
        BackoffType = DelayBackoffType.Exponential,
        UseJitter = true,
        OnRetry = args =>
        {
            _logger.LogWarning("Retry attempt {Attempt} due to {Reason}", 
                args.AttemptNumber, args.Outcome.Exception);
            return default;
        }
    });
});

8. 最佳实践与性能考虑 ​

8.1 拦截器性能优化 ​

✅ 应该做的:

  1. 保持拦截器轻量

    csharp
    // ✅ 好: 异步非阻塞
    public override async ValueTask<int> SavedChangesAsync(...)
    {
        await _eventBus.PublishAsync(events);
        return await base.SavedChangesAsync(...);
    }
    
    // ❌ 坏: 同步阻塞
    public override int SavedChangesAsync(...)
    {
        _eventBus.Publish(events).Wait();  // 阻塞线程
        return base.SavedChangesAsync(...);
    }
  2. 批量处理减少开销

    csharp
    // ✅ 好: 批量写入
    context.AuditRecords.AddRange(_auditRecords);
    await context.SaveChangesAsync();
    
    // ❌ 坏: 逐条写入
    foreach (var record in _auditRecords)
    {
        context.AuditRecords.Add(record);
        await context.SaveChangesAsync();  // N次数据库调用
    }
  3. 避免在拦截器中查询数据库

    csharp
    // ❌ 危险: 可能导致递归调用
    public override async ValueTask<InterceptionResult<int>> SavingChangesAsync(...)
    {
        var existingRecord = await context.AuditRecords.FindAsync(id);  // 可能触发新的拦截
        return await base.SavingChangesAsync(...);
    }
  4. 使用条件编译调试

    csharp
    #if DEBUG
    _logger.LogDebug("Interceptor executed: {Details}", details);
    #endif

8.2 拦截器组合与优先级 ​

csharp
// 推荐的服务注册顺序
builder.Services.AddDbContext<AppDbContext>((sp, options) =>
{
    options.UseSqlServer(connectionString);
    
    // 1. 连接层
    options.AddInterceptors(sp.GetRequiredService<ResilientConnectionInterceptor>());
    
    // 2. 命令层
    options.AddInterceptors(sp.GetRequiredService<PerformanceMonitoringInterceptor>());
    
    // 3. 保存层
    options.AddInterceptors(
        sp.GetRequiredService<SoftDeleteInterceptor>(),
        sp.GetRequiredService<AuditInterceptor>(),
        sp.GetRequiredService<MultiTenantInterceptor>(),
        sp.GetRequiredService<DomainEventPublisherInterceptor>()
    );
});

8.3 测试拦截器 ​

csharp
public class AuditInterceptorTests
{
    [Fact]
    public async Task Should_Create_Audit_Record_On_Insert()
    {
        // Arrange
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseInMemoryDatabase("TestDb")
            .AddInterceptors(new AuditInterceptor(mockHttpContext.Object))
            .Options;
        
        using var context = new AppDbContext(options);
        
        // Act
        context.Products.Add(new Product { Name = "Test" });
        await context.SaveChangesAsync();
        
        // Assert
        var audit = await context.AuditRecords.FirstOrDefaultAsync();
        Assert.NotNull(audit);
        Assert.Equal("Product", audit.EntityName);
        Assert.Equal("Added", audit.Action);
    }
}

总结 ​

拦截器是 EF Core 最强大的扩展机制之一:

核心优势 ​

✅ 横切关注点分离 - 业务逻辑与基础设施解耦
✅ 零侵入式设计 - 无需修改现有代码
✅ 高度可复用 - 一次编写,多处使用
✅ 灵活组合 - 多个拦截器协同工作

典型应用场景 ​

  1. 审计日志 - 自动追踪所有数据变更
  2. 软删除 - 透明转换硬删除为软删除
  3. 性能监控 - 捕获慢查询并告警
  4. 多租户 - 自动数据隔离
  5. 领域事件 - 解耦业务流程
  6. 连接弹性 - 自动重试 transient 故障

注意事项 ​

⚠️ 避免在拦截器中执行重型操作
⚠️ 注意拦截器执行顺序
⚠️ 防止递归调用和无限循环
⚠️ 做好异常处理和日志记录

掌握拦截器技术,你可以构建出更加优雅和可维护的企业级应用!

基于 MIT 许可发布