EF Core 同构集成与拦截器实战 (EF Core Integration & Interceptors)
对于习惯使用 Microsoft 官方 ORM 技术栈的团队,BitzOrcas.Modern 在 src/Framework/BitzOrcas.Infrastructure.EfCore 中提供了完整的生产级支持。它不仅完美实现了领域命令仓储接口 ICommandRepository<T, TId>,更通过一系列精巧的 SaveChanges 拦截器(Interceptors),将审计元数据回填、敏感数据(PII)加密与 CAP 事务发件箱无缝嵌入数据保存生命周期。
1. 实体 Fluent 配置:IEntityTypeConfiguration<T>
在契约层定义的领域实体,在基础设施层通过独立的配置类进行映射,避免领域逻辑依赖具体的 ORM 包:
using BitzOrcas.Sandbox.Domain;using Microsoft.EntityFrameworkCore;using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace BitzOrcas.Sandbox.Infrastructure.Mapping;
/// <summary>/// Note 实体的 EF Core 映射配置/// </summary>public sealed class NoteConfiguration : IEntityTypeConfiguration<Note>{ public void Configure(EntityTypeBuilder<Note> builder) { // 映射物理表名,严禁使用 T_ 前缀魔术命名 builder.ToTable("SandboxNote");
builder.HasKey(x => x.Id); builder.Property(x => x.Id) .ValueGeneratedNever(); // 使用雪花 ID 生成器,不依赖数据库自增
// 对应持久化别名列 Name 与最大长度 builder.Property(x => x.Title) .HasColumnName("Name") .HasMaxLength(Note.TitleMaxLength) .IsRequired();
builder.Property(x => x.TenantId) .HasMaxLength(64) .IsRequired();
// 乐观并发锁版本列与软删除 builder.Property(x => x.Version) .IsConcurrencyToken();
builder.Property(x => x.IsDeleted) .IsRequired(); }}2. 核心拦截器机制:审计回填与 PII 保护
BitzOrcas 在 BitzOrcasDbContext 中挂载了两个关键拦截器:
2.1 审计属性自动填充:AuditSaveChangesInterceptor
自动为所有实现 IAuditableEntity 的实体注入当前租户操作人与时间戳,避免业务 Handler 中手写赋值:
using System;using System.Threading;using System.Threading.Tasks;using BitzOrcas.Application.Abstractions.Tenancy;using BitzOrcas.Domain.Contracts;using Microsoft.EntityFrameworkCore;using Microsoft.EntityFrameworkCore.Diagnostics;
namespace BitzOrcas.Infrastructure.EfCore;
/// <summary>/// 审计元数据自动填充拦截器/// </summary>public sealed class AuditSaveChangesInterceptor(ICurrentTenant currentTenant) : SaveChangesInterceptor{ public override ValueTask<InterceptionResult<int>> SavingChangesAsync( DbContextEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = default) { if (eventData.Context is null) { return ValueTask.FromResult(result); }
var now = DateTimeOffset.UtcNow; var tenant = currentTenant.Tenant; var currentUserId = tenant.IsAvailable ? tenant.EffectiveTenantId : "system";
foreach (var entry in eventData.Context.ChangeTracker.Entries<IAuditableEntity>()) { if (entry.State == EntityState.Added) { entry.Entity.CreateTime = now; entry.Entity.CreateId = currentUserId; } else if (entry.State == EntityState.Modified) { entry.Entity.ModifyTime = now; entry.Entity.ModifyId = currentUserId; } }
return ValueTask.FromResult(result); }}2.2 敏感数据自动脱敏加密:PiiSaveChangesInterceptor
在数据入库前,拦截打标了 [PiiEncrypted] 属性的实体字段,使用 AES-GCM 进行对称加密,并在读取时自动解密,确保落盘数据即使发生数据库泄露也无法被明文破解。
3. 全局租户隔离过滤器 (Global Query Filter)
在 BitzOrcasDbContext.OnModelCreating 中,框架为所有继承 TenantAggregateRoot 的实体自动注册全局租户隔离过滤器:
protected override void OnModelCreating(ModelBuilder modelBuilder){ base.OnModelCreating(modelBuilder);
// 自动扫描当前程序集内的所有 IEntityTypeConfiguration modelBuilder.ApplyConfigurationsFromAssembly(typeof(BitzOrcasDbContext).Assembly);
// 为多租户实体自动附加全局隔离过滤器 foreach (var entityType in modelBuilder.Model.GetEntityTypes()) { if (typeof(ITenantEntity).IsAssignableFrom(entityType.ClrType)) { var parameter = System.Linq.Expressions.Expression.Parameter(entityType.ClrType, "e"); var filter = System.Linq.Expressions.Expression.Lambda( System.Linq.Expressions.Expression.Equal( System.Linq.Expressions.Expression.Property(parameter, nameof(ITenantEntity.TenantId)), System.Linq.Expressions.Expression.Property( System.Linq.Expressions.Expression.Constant(_tenantContext), nameof(ITenantContext.TenantId))), parameter);
entityType.SetQueryFilter(filter); } }}4. CAP 事务发件箱与工作单元 (EfCoreUnitOfWork)
为了保证领域事件派发与数据库写入的物理一致性,EfCoreUnitOfWork 将 EF Core 的 IDbContextTransaction 绑定至 CAP 事务:
public async Task<int> CommitAsync(CancellationToken cancellationToken = default){ // 开启物理数据库事务并接入 CAP 事务上下文 using var transaction = await _dbContext.Database.BeginTransactionAsync(_capPublisher, autoCommit: false, cancellationToken); try { var writtenCount = await _dbContext.SaveChangesAsync(cancellationToken); await transaction.CommitAsync(cancellationToken); return writtenCount; } catch { await transaction.RollbackAsync(cancellationToken); throw; }}5. 相关架构决策与进阶推荐 (Related Deep Dives)
- 架构选型:多 ORM 双引擎架构与选型矩阵
- Dapper 只读实践:Dapper 只读连接池与极限查询优化
- 决策溯源:ADR 0304:事务发件箱与领域事件派发规范