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
相关产品推荐
相关产品推荐

