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

.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;
    }
    // ...其他操作
}

用户疑问

  1. 为何全局设置了NoTracking仍会产生跟踪冲突?
  2. 有何解决建议?
  3. 是否需要放弃依赖注入改用临时上下文?

解决方案

问题根源

  1. Find方法的特性:db.CcpManufacturers.Find(NewMan.MfrId)会自动跟踪查询到的实体——即便全局设置了NoTracking,Find方法优先从上下文跟踪缓存查找,查询到新实体后会自动加入跟踪缓存。
  2. 上下文生命周期:如果OracleContext被误注册为单例(AddDbContext默认是Scoped),会导致整个应用共用一个上下文实例,跟踪缓存不会随请求清空,重复操作时易出现冲突。
  3. 循环重复操作: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 10:45:39