EF核心密钥已被跟踪

时间:2020-08-11 07:18:23

标签: c# ef-core-3.1

使用EF Core 3.1.6时发生错误

我也尝试在Entity上分离,但该错误未修复 不可能重用跟踪的密钥吗?

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
}

2 个答案:

答案 0 :(得分:0)

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

在第一种情况下,您将数据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
}

答案 1 :(得分:0)

此代码存在各种类型的问题,但是关于您所要解决的问题-您似乎已经获取了一系列实体,然后遍历它们并再次一个接一个地获取它们并做错了(如Neistow所指出的)。不管第二次获取是否在不进行跟踪的情况下进行/都已分离,一旦您.AddOrUpdate()实体,EF都会尝试开始对其进行跟踪,并且由于您已经用相同的密钥跟踪了另一个实例,因此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.

您的初始实体范围不会被跟踪,因此以后在重新获取时不会发生键冲突。