如何在dotnet core的ISaveChangesInterceptor SavedChangesAsync中识别已变更实体
问题原因
EF Core 在完成数据库写入操作后,会自动将所有状态为Added、Modified的实体状态重置为Unchanged,这是框架的默认设计,所以你在SavedChangesAsync中无法通过EntityState.Added筛选到刚新增的实体。
而SavingChangesAsync触发于数据库写入前,自然拿不到数据库生成的字段值,也无法确认保存操作的最终结果。
解决方案
最通用的实现方式是:在SavingChangesAsync中先暂存本次操作涉及的新增、修改实体条目,等SavedChangesAsync触发时再处理这些暂存的条目,此时实体已完成写入,数据库生成的字段已被EF Core自动回写完成,且能进入SavedChangesAsync就代表保存操作已经成功。
需要用AsyncLocal存储暂存的条目,保证不同异步请求上下文的数据隔离,避免并发场景下数据错乱。
完整实现代码
public class CustomSaveChangesInterceptor : ISaveChangesInterceptor { // 异步本地存储,保证多请求并发时的数据隔离 private readonly AsyncLocal<List<EntityEntry>> _pendingTrackedEntries = new(); public ValueTask<InterceptionResult<int>> SavingChangesAsync( DbContextEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = default) { // 保存前收集所有新增、修改的实体条目,只需要新增的话只保留EntityState.Added即可 var trackedEntries = eventData.Context.ChangeTracker.Entries() .Where(entry => entry.State is EntityState.Added or EntityState.Modified) .ToList(); _pendingTrackedEntries.Value = trackedEntries; return ValueTask.FromResult(result); } public async ValueTask<int> SavedChangesAsync( SaveChangesCompletedEventData eventData, int result, CancellationToken cancellationToken = default) { var entries = _pendingTrackedEntries.Value; if (entries != null) { foreach (var entry in entries) { // entry即为已成功写入的实体,数据库生成的字段(自增ID、计算列、默认值等)均已完成赋值 var entity = entry.Entity; // 此处写入你的业务逻辑,如审计日志记录、领域事件触发等 } // 清空暂存数据避免内存泄漏 _pendingTrackedEntries.Value = null; } return result; } // 保存失败时清空暂存数据 public ValueTask SaveChangesFailedAsync( DbContextErrorEventData eventData, CancellationToken cancellationToken = default) { _pendingTrackedEntries.Value = null; return ValueTask.CompletedTask; } // 同步方法按需实现,如果项目只有异步操作可以忽略同步实现 public InterceptionResult<int> SavingChanges(DbContextEventData eventData, InterceptionResult<int> result) { var trackedEntries = eventData.Context.ChangeTracker.Entries() .Where(entry => entry.State is EntityState.Added or EntityState.Modified) .ToList(); _pendingTrackedEntries.Value = trackedEntries; return result; } public int SavedChanges(SaveChangesCompletedEventData eventData, int result) { var entries = _pendingTrackedEntries.Value; if (entries != null) { foreach (var entry in entries) { // 同步场景的业务逻辑 } _pendingTrackedEntries.Value = null; } return result; } public void SaveChangesFailed(DbContextErrorEventData eventData) { _pendingTrackedEntries.Value = null; } }
注册方式
在添加DbContext服务时注册拦截器即可生效:
builder.Services.AddDbContext<YourDbContext>(options => { options.UseSqlServer("你的连接字符串"); options.AddInterceptors(new CustomSaveChangesInterceptor()); });
内容的提问来源于stack exchange,提问作者soynerdito
相关产品推荐
相关产品推荐

