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

如何在Entity Framework Core 6.0中用Parallel Loop更新元素列表

解决EF Core并行更新DbContext引发的并发安全错误

错误原因

EF Core的DbContext实例是非线程安全的,其内部用于跟踪实体状态的集合不支持并发修改。你用Parallel.ForEach多线程同时调用context.Update(item),会导致多个线程同时修改DbContext的内部状态集合,最终触发状态损坏的报错。

解决方案

方案1:EF Core 6.0批量更新(推荐,性能最优)

因为EF Core 6.0无内置批量更新API,推荐以下两种批量处理方式:

方式A:使用第三方批量操作库

先安装NuGet包:Install-Package EFCore.BulkExtensions

using (var context = new ApplicationDbContext())
{
    // 筛选并设置需要更新的实体状态
    var inStockItems = await context.Items
        .Where(i => Helper.checkList.Contains(i.Link))
        .ToListAsync();
    foreach (var item in inStockItems)
    {
        item.StatusId = (int)Emuns.Status.InStock;
    }

    var outStockItems = await context.Items
        .Where(i => !Helper.checkList.Contains(i.Link))
        .ToListAsync();
    foreach (var item in outStockItems)
    {
        item.StatusId = (int)Emuns.Status.OutStock;
    }

    // 批量提交更新
    await context.BulkUpdateAsync(inStockItems);
    await context.BulkUpdateAsync(outStockItems);
}

方式B:手动分批次更新(无需第三方库)

将数据分批次加载并更新,避免一次性加载6万条数据占用过多内存:

using (var context = new ApplicationDbContext())
{
    int batchSize = 1000;
    int totalCount = await context.Items.CountAsync();
    int batchCount = (int)Math.Ceiling((double)totalCount / batchSize);

    for (int i = 0; i < batchCount; i++)
    {
        var batchItems = await context.Items
            .Skip(i * batchSize)
            .Take(batchSize)
            .ToListAsync();

        foreach (var item in batchItems)
        {
            item.StatusId = Helper.checkList.Contains(item.Link) 
                ? (int)Emuns.Status.InStock 
                : (int)Emuns.Status.OutStock;
        }

        await context.SaveChangesAsync();
        context.ChangeTracker.Clear(); // 清理跟踪对象,释放内存
    }
}

方案2:并行处理+独立DbContext(不推荐,性能差)

如果必须使用并行循环,需为每个线程创建独立的DbContext实例,避免多线程共享同一个非线程安全上下文:

var itemList = await context.Items.ToListAsync();

Parallel.ForEach(itemList, (item) =>
{
    using (var threadContext = new ApplicationDbContext())
    {
        var dbItem = threadContext.Items.Find(item.Id);
        if (dbItem != null)
        {
            dbItem.StatusId = Helper.checkList.Contains(item.Link) 
                ? (int)Emuns.Status.InStock 
                : (int)Emuns.Status.OutStock;
            
            threadContext.SaveChanges();
        }
    }
});

该方式会产生6万次数据库请求,性能极低,仅在特殊场景下考虑使用。

方案3:并行分块处理(优化方案2性能)

将数据分块,每个块使用独立DbContext处理,减少DbContext创建次数和数据库请求次数:

var itemList = await context.Items.ToListAsync();
int chunkSize = 100;
var chunks = itemList.Chunk(chunkSize); // .NET 6+支持Chunk方法

Parallel.ForEach(chunks, (chunk) =>
{
    using (var threadContext = new ApplicationDbContext())
    {
        foreach (var item in chunk)
        {
            var dbItem = threadContext.Items.Find(item.Id);
            if (dbItem != null)
            {
                dbItem.StatusId = Helper.checkList.Contains(item.Link) 
                    ? (int)Emuns.Status.InStock 
                    : (int)Emuns.Status.OutStock;
            }
        }
        threadContext.SaveChanges(); // 每块批量保存
    }
});

总结

优先选择批量更新方案,既能避免线程安全问题,又能保证更新效率。并行循环方案仅在无法使用批量更新的特殊场景下考虑,且需严格保证每个线程使用独立DbContext。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 04:01:17