【问题标题】:EF Core Key is already being trackedEF Core Key 已被跟踪
【发布时间】:2020-08-11 07:18:23
【问题描述】:

使用 EF Core 3.1.6 时出错

我也尝试过 Detach on Entity,但错误未修复 是不是不能重复使用被跟踪的key?

The instance of entity type 'Data' cannot be tracked because another instance with the key value '{Id : 1657126}' is already being tracked. When attaching e
xisting entities, ensure that only one entity instance with a given key value is attached.

Proc.cs

public async Task ProcData(IReadOnlyCollection<DataObject> datas) {
  var ctx = new DbContext();
  var transaction = await ctx.Database.BeginTransactionAsync();

  for(var i = 0; i < datas.Count; i++) {
    var data = datas.ElementAt(i);
    var g_Data =
          await ctx.Data.SingleOrDefaultAsync(o =>
            o.Id== o.Id) ??
          new Data{Id = data.Id};
    ctx.AddOrUpdate(g_Data); // Error
  }
  await ctx.SaveChangesAsync();

  await Process(ctx);
  try
  {
    await transaction.CommitAsync();
  }
  catch (Exception e)
  {
    await transaction.RollBackAsync();
  }
}

private async Task Process(DbContext ctx) {
  // something
}

【问题讨论】:

    标签: c# ef-core-3.1


    【解决方案1】:

    您不能在 DB 中跟踪并插入具有相同 ID(主键)的两个实体。

    在第一种情况下,您将 Data 的 Id 与其自身进行比较,这是无用的,这是错误的根源。

    尝试改变

    var g_Data = await ctx.Data.SingleOrDefaultAsync(o => o.Id == o.Id) ?? new Data{Id = data.Id};
    

    var g_Data = await ctx.Data.SingleOrDefaultAsync(o => o.Id == data.Id) ?? new Data{Id = data.Id};
    

    编辑:

    有一个很好的方法可以通过添加 foreach 循环来改进你的代码。

    public async Task ProcData(IReadOnlyCollection<DataObject> datas) {
      var ctx = new DbContext();
      var transaction = await ctx.Database.BeginTransactionAsync();
      foreach(var data in datas)
      {
        var g_Data = await ctx.Data.SignleOrDefault(o => o.Id = data.Id) ?? new Data{Id == data.Id}
        ctx.AddOrUpdate(g_Data)
      }
      await ctx.SaveChangesAsync();
    
      await Process(ctx);
      try
      {
        await transaction.CommitAsync();
      }
      catch (Exception e)
      {
        await transaction.RollBackAsync();
      }
    }
    
    private async Task Process(DbContext ctx) {
      // something
    }
    

    【讨论】:

      【解决方案2】:

      此代码存在所有类型的问题,但关于您的问题 - 似乎您已经获取了一系列实体,然后您遍历它们并逐个获取它们再次 em> 并且做错了(正如 Neistow 已经指出的那样)。无论第二次提取是否在没有跟踪的情况下完成/是否分离,一旦您 .AddOrUpdate() 您的实体,EF 将尝试开始跟踪它并且将失败,因为您已经使用相同的密钥跟踪了另一个实例。

      这里有很多有效的方法。你可以:

      1. 重用您提取的实体。
        从您提供的代码中不清楚,但我假设 DataObject 是数据传输对象类型,Data 是您的实体类型。您可以直接重用您的实体。但是,我很难准确地说出它的外观,因为您尝试立即在此处进行插入和更新,而区分新实体和现有实体的唯一方法是从数据库中重新获取它们(这是导致你的问题)。理想情况下,您应该已经知道哪些 DTO 是新的,AddRange() 您的新 Data 实体仅基于它们,然后是 SaveChangesAsync()。由于 EF 会跟踪您现有的实体,因此即使没有明确要求更新,它们也会得到更新。
      2. 使用 FindAsync() - 与 SingleOrDefaultAsync() 不同,它会检查您的本地/跟踪实体并从 EF 缓存中获取它们,而不是直接进入您的数据库。
      var g_Data = await ctx
          .Data
          .FindAsync(d => d.Id == data.Id);
      
      1. 不跟踪原始集合。您没有在此处提供执行第一次提取的代码,但如果它看起来像这样:
      var dataObjects = await ctx
          .Data
          .AsNoTracking() // <<-- Do not track the entities you fetch.
          .Where(do => myPredicate)
          .ToListAsync()
          // Transform to DTOs.
      

      您的初始实体范围不会被跟踪,因此您稍后重新获取时不会出现密钥冲突。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-09-15
        • 1970-01-01
        • 2018-12-24
        • 2021-12-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-02-28
        相关资源
        最近更新 更多