.NET 6 EF Core跟踪异常求助:依赖注入下重复跟踪报错
EF Core跟踪机制问题排查
问题背景
已通过依赖注入配置OracleContext并设置QueryTrackingBehavior.NoTracking,但在通过context.RawSqlQuery获取对象列表后,遍历调用业务方法创建新实体时,执行context.Add报错:
"The instance of entity type 'Whatever' cannot be tracked because another instance with the key value '{MfrId: 90}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached."
尝试过修改ChangeTracker跟踪行为、手动分离实体等操作均无效。
相关代码
上下文配置代码
builder.Services.AddDbContext<OracleContext>(options => options.UseOracle(OracleConnectionString, b => b.UseOracleSQLCompatibility("11")) .LogTo(s => System.Diagnostics.Debug.WriteLine(s)) .EnableDetailedErrors(Settings.Dev_System) .EnableSensitiveDataLogging(Settings.Dev_System) .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));
报错业务方法代码
public class AssetMethods : IAssetMethods { public OracleContext db; public AssetMethods(OracleContext context) { db = context; } public CcpManufacturer? CreateNewManufacturer(CcpManufacturer NewMan, string ActorID) { // 非数据库验证代码已移除... // 检查是否已存在 CcpManufacturer? OldMan = db.CcpManufacturers.Where(m=>m.MfrName == NewMan.MfrName).AsNoTracking().FirstOrDefault(); if (OldMan != null) { return OldMan; } // 设置创建信息 NewMan.CreatedBy = ActorID; NewMan.CreationDate = DateTime.Now; NewMan.Isenabled = true; // 保存 db.CcpManufacturers.Add(NewMan); db.SaveChanges(); // 此处报错 // 防止XSS反射 return db.CcpManufacturers.Find(NewMan.MfrId); } }
调用代码
List<MasterItem> Items = OM.RawSqlQuery(Query, x => new MasterItem { MFR_NAME = (string)x[0], MODEL_NUMBER = (string)x[1], LEGAL_NAME= (string)x[2]}); foreach (MasterItem item in Items) { CcpManufacturer? Man = new() { MfrName = item.MFR_NAME, Displayname = item.MFR_NAME }; Man = AM.CreateNewManufacturer(Man,System.Id); // 创建新实体时报错 if (Man == null || Man.MfrId == 0) { continue; } // ...其他操作 }
用户疑问
- 为何全局设置了
NoTracking仍会产生跟踪冲突? - 有何解决建议?
- 是否需要放弃依赖注入改用临时上下文?
解决方案
问题根源
Find方法的特性:db.CcpManufacturers.Find(NewMan.MfrId)会自动跟踪查询到的实体——即便全局设置了NoTracking,Find方法优先从上下文跟踪缓存查找,查询到新实体后会自动加入跟踪缓存。- 上下文生命周期:如果
OracleContext被误注册为单例(AddDbContext默认是Scoped),会导致整个应用共用一个上下文实例,跟踪缓存不会随请求清空,重复操作时易出现冲突。 - 循环重复操作:
Items中若存在重复的MFR_NAME,第一次创建的实体会被上下文跟踪,第二次检查时即便OldMan用了AsNoTracking,后续返回的实体或上下文缓存中的旧实例仍会引发冲突。
解决步骤
1. 替换Find为无跟踪查询
将返回实体的代码改为无跟踪查询,避免自动添加到跟踪缓存:
// 替换原Find方法 return db.CcpManufacturers.AsNoTracking().FirstOrDefault(m => m.MfrId == NewMan.MfrId);
2. 确认上下文生命周期
确保OracleContext注册为Scoped(AddDbContext默认配置),Web应用中Scoped上下文会在每个请求结束后释放,自动清空跟踪缓存。
3. 手动处理跟踪冲突(可选)
若必须保留跟踪逻辑,在添加新实体前检查并分离已跟踪的同主键实体:
// 在Add前添加检查逻辑 var trackedEntry = db.ChangeTracker.Entries<CcpManufacturer>().FirstOrDefault(e => e.Entity.MfrId == NewMan.MfrId); if (trackedEntry != null) { trackedEntry.State = EntityState.Detached; } db.CcpManufacturers.Add(NewMan);
4. 优化循环去重
提前对Items按MFR_NAME去重,避免重复调用创建方法,减少不必要的数据库操作:
// 先去重再遍历 var uniqueItems = Items.GroupBy(item => item.MFR_NAME).Select(g => g.First()).ToList(); foreach (MasterItem item in uniqueItems) { // ...后续逻辑 }
5. 使用临时上下文(极端场景)
若Scoped上下文仍有问题,可在业务方法内创建临时上下文实例,确保每次操作独立:
public CcpManufacturer? CreateNewManufacturer(CcpManufacturer NewMan, string ActorID) { using var tempDb = new OracleContext(/* 传入配置参数 */); tempDb.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; // 检查是否已存在 CcpManufacturer? OldMan = tempDb.CcpManufacturers.Where(m=>m.MfrName == NewMan.MfrName).FirstOrDefault(); if (OldMan != null) { return OldMan; } // 设置创建信息并保存 NewMan.CreatedBy = ActorID; NewMan.CreationDate = DateTime.Now; NewMan.Isenabled = true; tempDb.CcpManufacturers.Add(NewMan); tempDb.SaveChanges(); return tempDb.CcpManufacturers.AsNoTracking().FirstOrDefault(m => m.MfrId == NewMan.MfrId); }
内容的提问来源于stack exchange,提问作者Joshua Fellers
相关产品推荐
相关产品推荐

