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

并行处理与Entity Framework:锁定含EntityState.Added状态的实体

核心问题根源

Entity Framework 中的DbContext在设计上就是非线程安全的,你直接在Parallel.ForEach中共享同一个上下文实例,哪怕对上下文本身加锁,也无法避免EF内部状态管理的逻辑出现不可预期的状态冲突,这是你之前加锁方案不生效的核心原因。


可行的两种解决方案

方案1:保留单上下文结构,修复锁逻辑

不要锁EF提供的上下文、DbSet、ObjectStateManager这类内部对象,这些对象的内部实现可能会变更锁状态,可靠性极低,你应该使用独立的锁对象保证「检查-新增」操作的原子性,还可以搭配线程安全的缓存提升性能:

  1. 定义独立的锁对象和缓存字典:
// 独立锁对象,不要和任何EF内部对象复用
private static readonly object _guidMappingLock = new object();
// 线程安全缓存,避免每次都遍历EF的状态管理器,性能更高
private static readonly ConcurrentDictionary<string, Guid> _identGuidCache = new ConcurrentDictionary<string, Guid>();
  1. 修改查询和新增逻辑,保证整个操作在锁内完成:
private static GuidMapping GetOrCreateGuidMapping(MappingEntities context, string itemIdent)
{
    // 先查缓存,命中直接返回
    if (_identGuidCache.TryGetValue(itemIdent, out var existedGuid))
    {
        return new GuidMapping { ItemIdent = itemIdent, Guid = existedGuid };
    }

    lock (_guidMappingLock)
    {
        // 锁内二次检查缓存,避免多线程等待时已被其他线程新增
        if (_identGuidCache.TryGetValue(itemIdent, out existedGuid))
        {
            return new GuidMapping { ItemIdent = itemIdent, Guid = existedGuid };
        }

        // 查数据库已有记录
        var dbExisted = context.GuidMapping.FirstOrDefault(m => m.ItemIdent == itemIdent);
        if (dbExisted != null)
        {
            _identGuidCache.TryAdd(itemIdent, dbExisted.Guid);
            return dbExisted;
        }

        // 查内存中已添加未保存的记录
        var addedExisted = ((IObjectContextAdapter)context)
            .ObjectContext
            .ObjectStateManager
            .GetObjectStateEntries(EntityState.Added)
            .Where(ent => ent.Entity is GuidMapping)
            .Select(ent => ent.Entity as GuidMapping)
            .FirstOrDefault(m => m.ItemIdent == itemIdent);
        if (addedExisted != null)
        {
            _identGuidCache.TryAdd(itemIdent, addedExisted.Guid);
            return addedExisted;
        }

        // 都不存在则创建新记录
        var newMapping = new GuidMapping { ItemIdent = itemIdent, Guid = Guid.NewGuid() };
        context.GuidMapping.Add(newMapping);
        _identGuidCache.TryAdd(itemIdent, newMapping.Guid);
        return newMapping;
    }
}

方案2:规避单上下文多线程问题(更推荐)

DbContext本身是轻量级对象,创建销毁开销极低,你可以给并行处理的每个线程单独创建独立的上下文实例,最后汇总所有生成的GuidMapping记录,全局去重后再统一批量插入:

  • 并行处理阶段每个线程自己查自己的上下文,不需要任何锁,性能更高
  • 全部处理完成后把所有生成的映射记录按ItemIdent去重,再用单上下文批量插入到数据库,完全避免并发冲突

额外注意事项

无论用哪种方案,最后调用SaveChanges之前都建议再做一次全局的ItemIdent去重校验,避免边缘场景下的重复数据问题。

内容的提问来源于stack exchange,提问作者CarenRose

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 21:57:00