You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 21:06:01