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

EF8 PostgreSQL批量插入时AuditableInterceptor集合修改错误求助

问题根源分析

你的核心问题在于DbContext实例并非线程安全,但你通过Task.WhenAll同时启动7000个异步任务,共享同一个_context实例操作ChangeTracker。即使本次操作的实体不属于审计类,AuditableInterceptor中context.ChangeTracker.Entries<BaseAuditableEntity>()的枚举操作,会在其他任务并发修改ChangeTracker集合时触发"Collection was modified"错误。

解决方案

方案1:为每个任务分配独立的DbContext实例

DbContext的设计不支持多线程并发操作,必须为每个Seed任务创建独立的实例。可以通过IDbContextFactory实现:

修改服务注入与Seed方法

// 注入IDbContextFactory而非直接注入DbContext
private readonly IDbContextFactory<YourDbContext> _contextFactory;

public CityService(IDbContextFactory<YourDbContext> contextFactory)
{
    _contextFactory = contextFactory;
}

public async Task Seed(DeliveryCityDTO city)
{
    // 为每个任务创建独立的DbContext
    using var context = await _contextFactory.CreateDbContextAsync();
    
    var deliveryService = await context.DeliveryServices.SingleAsync(x => x.Name == city.DeliveryServiceName);

    DeliveryCity deliveryCity = new()
    {
        CityFiasСode = city.CityFiasСode,
        Name = city.Name,
    };

    context.DeliveryCities.Add(deliveryCity);

    context.DeliveryServiceDeliveryCities.Add(new DeliveryServiceDeliveryCity
    {
        RefDeliveryCity = deliveryCity.Id,
        RefDeliveryService = deliveryService.Id
    });

    await context.SaveChangesAsync();
}

方案2:优化为批量插入(推荐)

原方案每个对象调用一次SaveChangesAsync,会产生7000次数据库请求,性能极低。改为批量处理,大幅减少数据库交互次数:

批量插入实现代码

public async Task BulkSeed(List<DeliveryCityDTO> cities)
{
    using var context = await _contextFactory.CreateDbContextAsync();
    
    // 批量查询所有需要的配送服务,避免重复查询数据库
    var uniqueServiceNames = cities.Select(c => c.DeliveryServiceName).Distinct().ToList();
    var serviceMap = await context.DeliveryServices
        .Where(s => uniqueServiceNames.Contains(s.Name))
        .ToDictionaryAsync(s => s.Name);

    const int batchSize = 1000; // 每1000条提交一次
    for (int i = 0; i < cities.Count; i += batchSize)
    {
        var batch = cities.Skip(i).Take(batchSize);
        
        foreach (var city in batch)
        {
            if (!serviceMap.TryGetValue(city.DeliveryServiceName, out var deliveryService))
                continue; // 处理找不到服务的异常情况
            
            var deliveryCity = new DeliveryCity
            {
                CityFiasСode = city.CityFiasСode,
                Name = city.Name
            };

            context.DeliveryCities.Add(deliveryCity);
            context.DeliveryServiceDeliveryCities.Add(new DeliveryServiceDeliveryCity
            {
                RefDeliveryCity = deliveryCity.Id,
                RefDeliveryService = deliveryService.Id
            });
        }
        
        await context.SaveChangesAsync();
        context.ChangeTracker.Clear(); // 清除已提交的实体,避免ChangeTracker内存过载
    }
}

临时修复:修改AuditableInterceptor避免枚举冲突

如果暂时无法调整DbContext使用方式,可以先修改Interceptor代码,将Entries转为列表后再操作,避免枚举时集合被修改:

// 原代码
// if (context.ChangeTracker.Entries<BaseAuditableEntity>().Count() == 0) return;

// 修改后
var auditableEntries = context.ChangeTracker.Entries<BaseAuditableEntity>().ToList();
if (auditableEntries.Count == 0) return;

// 后续操作基于auditableEntries列表进行

注意:此方案仅为临时规避错误,无法解决DbContext并发使用的根本问题,长期来看必须使用方案1或2。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 19:32:55