Entity Framework Core:同一实体多实例跟踪异常排查
问题场景
开发ASP.NET Core应用,使用Entity Framework Core进行数据访问,在编辑FiscalYear实体时遇到跟踪冲突异常。相关代码如下:
EditFiscalYear方法
public async Task<Response> EditFiscalYear(FiscalYear fiscalYear) { try { var validate = await EditFiscalYearValidate(new FiscalYearValidationModel { Object = fiscalYear }); if (validate.StatusCode == System.Net.HttpStatusCode.BadRequest) return validate; _context.EditEntity(fiscalYear); await _context.SaveChangesAsync(); return SuccessResponse(resultObject: fiscalYear, resultText: "Fiscal Year Updated Successfully"); } catch (Exception ex) { return FaultResponse(ex.Message); } }
验证方法
public async Task<Response> EditFiscalYearValidate(FiscalYearValidationModel model) { if ((await _context.PayPeriods .AnyAsync(x => x.FiscalYearId == model.Object.FiscalYearId))) { return FaultResponse("Fiscal Year Dependency found"); } var fiscalYear = await _context.FiscalYears.FirstOrDefaultAsync(x => x.FiscalYearId == model.Object.FiscalYearId); if (fiscalYear is null) return NotFoundResponse("No Fiscal Year Found"); // Other validation checks... return SuccessResponse(); }
EditEntity实现
public void EditEntity<TEntity>(TEntity entity, List<string> excludedParams = null) { var entry = this.Entry(entity); entry.State = EntityState.Modified; //some other modifications }
异常信息
"The instance of entity type 'FiscalYear' cannot be tracked because another instance with the same key value for {'FiscalYearId'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values."
核心疑惑
已知异常是EF Core同时跟踪同一Key的两个实体实例导致,但不解的是:当验证方法中不将FirstOrDefaultAsync的结果存入变量时,不会触发异常:
await _context.FiscalYears.FirstOrDefaultAsync(x => x.FiscalYearId == model.Object.FiscalYearId);
原因解析
存变量时的冲突逻辑
当你将查询结果赋值给fiscalYear变量时,该变量会持有实体实例的强引用。即使EditFiscalYearValidate方法执行完毕,变量超出作用域,但在垃圾回收(GC)触发前,实体实例仍然存在。此时EF Core的ChangeTracker会持续跟踪这个实例,因为实例本身未被回收。后续调用EditEntity尝试附加传入的fiscalYear实例时,ChangeTracker已存在同Key的跟踪实例,因此触发冲突异常。
不存变量时的无冲突逻辑
不将查询结果赋值给变量时,FirstOrDefaultAsync返回的实体实例没有任何外部强引用。方法执行完毕后,该实例立即成为无引用对象,GC会快速回收它。与此同时,EF Core的ChangeTracker会自动移除对该实例的跟踪(因为实例已不存在)。后续附加传入的实例时,ChangeTracker中已无同Key的跟踪实例,因此不会触发冲突。
解决方案的有效性
你使用的AsNoTracking()方案之所以有效,是因为它告诉EF Core不要跟踪查询返回的实体实例。即使将结果赋值给变量,ChangeTracker中也不会创建该实例的跟踪条目。后续附加传入的fiscalYear实例时,自然不会出现同Key实例的跟踪冲突。
内容的提问来源于stack exchange,提问作者anas1149

