实体类型“Item”的实例无法被跟踪,因为已经有另一个具有相同键值({'Id'})的实例正在被跟踪。

26

我知道这个问题已经被问过了,但是那个解决方案对我没有帮助。

[Fact]
public async Task UpdateAsync()
{
    string newTitle = "newTitle1";
    int newBrandId = 3;
    var item = await storeContext.Items.AsNoTracking().FirstOrDefaultAsync();
    item.BrandId = newBrandId;
    item.Title = newTitle;
    storeContext.Entry(item).State = EntityState.Detached;
    await service.UpdateAsync(item); // exception inside
    var updatedItem = await storeContext.Items.AsNoTracking().FirstOrDefaultAsync();
    Assert.Equal(newTitle, updatedItem.Title);
    Assert.Equal(newBrandId, updatedItem.BrandId);
}

public async Task UpdateAsync(T entity)
{
    _dbContext.Entry(entity).State = EntityState.Modified; // exception when trying to change the state
    await _dbContext.SaveChangesAsync();
}

错误信息:System.InvalidOperationException:无法跟踪实体类型“Item”的实例,因为已经跟踪了具有相同键值对 {'Id'} 的另一个实例。在附加现有实体时,请确保只附加具有给定键值的一个实体实例。考虑使用“DbContextOptionsBuilder.EnableSensitiveDataLogging”以查看冲突的键值。

有趣的是,即使没有从数据库中检索到任何项目,异常仍然是相同的,如下所示:

//var item = await storeContext.Items.AsNoTracking().FirstOrDefaultAsync();
  var item = new Item()
  {
      Id = 1,
      BrandId = newBrandId,
      CategoryId = 1,
      MeasurementUnitId = 1,
      StoreId = 1,
      Title = newTitle
  };

尝试删除此 storeContext.Entry(item).State = EntityState.Detached;,然后像您在问题末尾所做的那样手动创建项目。 - Tomas Chabada
1
请确保在您的单元测试storeContext中使用的DbContext实例与您的服务service中使用的实例相同。 - CodeNotFound
确保没有其他线程可以持有此对象。 - Tomas Chabada
@tomassino 一样,没有改变。即使只运行一个测试,错误仍然存在,所以我怀疑是线程的问题。 - Alexander Kozachenko
@CodeNotFound,它是一样的,只是包装在存储库中。 - Alexander Kozachenko
10个回答

30

在EF Core 2.2中遇到了同样的问题。我从未在其他应用程序中遇到过这种情况。

最终以某种方式重写了所有我的更新函数,就像这样:

public bool Update(Entity entity)
{
    try
    {   
       var entry = _context.Entries.First(e=>e.Id == entity.Id);
       _context.Entry(entry).CurrentValues.SetValues(entity);
       _context.SaveChanges();
       return true;
    }
    catch (Exception e)
    {
         // handle correct exception
         // log error
         return false;
    }
}

4
你是我的英雄。 - Aa Yy
2
这个方法在其他所有尝试都失败的情况下对我有效。同样是EF Core 2.2版本。感谢你,老兄!另外,我的DBContext条目全部设置为Scoped而非Singleton,顺便说一下。即使微软也建议使用Scoped,但在我的情况下已经这么做了。 - AussieJoe
2
运行得非常好! - Rapid99
1
太好了!谢谢。 - Dmitry
1
不要使用 .First(e=>e.Id == entity.Id); ,最好使用 .Find(entity.Id); - Fitri Halim

10

阿历克斯的答案是完全禁用跟踪,这解决了我的问题,但我开始担心,因为我不知道这会如何影响我的应用程序的其他部分。所以我去了微软文档,找到了这里

如果您要操作实体实例并将更改持久化到数据库中,请不要禁用更改跟踪。

此方法设置所有使用这些选项创建的上下文的默认行为,但可以使用QueryTrackingBehavior在上下文实例上覆盖此行为,也可以使用AsNoTracking(IQueryable)和AsTracking(IQueryable)方法在单个查询上覆盖此行为。

因此,对我来说解决方案是只在需要时禁用跟踪。因此,我通过在检索相同条目的代码的其他部分中使用以下内容来解决我的问题:

var entry = await context
    .SomeDbTable
    .AsNoTracking() // this is what you're looking for
    .Find(id);

8

我遇到的许多问题都有一个恶劣的根源。 简而言之:我已经深刻认识到为什么dbContext是作用域而不是单例。这里是Store类型,但问题是相同的。 这是简化的测试初始化代码。

public TestBase()
{
    services = new ServiceCollection();
    storeContext = StoreContextMock.ConfigureStoreContext(services, output);
    serviceProvider = services.BuildServiceProvider();
}
public static StoreContext ConfigureStoreContext(IServiceCollection services)
{
    services.AddDbContext<StoreContext>(c =>
        c.UseInMemoryDatabase(Guid.NewGuid().ToString()).UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));

    var serviceProvider = services.BuildServiceProvider();
    var storeContext = serviceProvider.GetRequiredService<StoreContext>();
    storeContext .Stores.Add(new Store { Title = "John's store", Address = "NY", Description = "Electronics best deals", SellerId = "john@mail.com" });
    storeContext .Stores.Add(new Store { Title = "Jennifer's store", Address = "Sydney", Description = "Fashion", SellerId = "jennifer@mail.com" });
    storeContext .SaveChanges();
    return storeContext ;
}

我重新阅读了错误信息,最终注意到了主要单词

'Store'实体类型的实例无法被跟踪,因为已经有另一个具有相同键值{'Id'}的实例正在被跟踪

所以必须有一些孤立的被跟踪实例阻止我使用store。我没有保存任何对或的引用,因此必须是storeContext在声明和初始化后仍然存储插入对象的引用。这就是为什么我无法正常更新变量以及为什么我的从数据库查询的对象都分配了它们的导航属性(惰性加载与此无关)。以下代码解决了我所有的问题。

public static StoreContext ConfigureStoreContext(IServiceCollection services)
{
    services.AddDbContext<StoreContext>(c =>
        c.UseInMemoryDatabase(Guid.NewGuid().ToString()).UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking));

    var serviceProvider = services.BuildServiceProvider();
    var storeContext = serviceProvider.GetRequiredService<StoreContext>();
    var s1 = new Store { Title = "John's store", Address = "NY", Description = "Electronics best deals", SellerId = "john@mail.com" };
    var s2 = new Store { Title = "Jennifer's store", Address = "Sydney", Description = "Fashion", SellerId = "jennifer@mail.com" }
    storeContext .Stores.Add(s1);
    storeContext .Stores.Add(s2);
    storeContext .Entry<Store>(s1).State = EntityState.Detached;
    storeContext .Entry<Store>(s2).State = EntityState.Detached;
    storeContext .SaveChanges();
    return storeContext ;
}

那就是为什么dbContext应该被限制在一个范围内的许多原因之一。感谢提示

如何在关闭跟踪时自动更新一对多关系中的子项? - sajjad
微软的文档表示:“如果你想操作实体实例并将这些更改持久化到数据库中,那么你不应该禁用更改跟踪。” - AskYous

6

对我来说,这是解决方案:

public void Update(int id, T obj)
        {
            var entry = table.Find(id);
            _context.Entry(entry).CurrentValues.SetValues(obj);
        }

根据Bryan提供的解决方案,我认为我使用了更新版本的EF / Automapping。这对我有用。


这是在这篇非常全面的文章中描述的选项之一:https://learn.microsoft.com/en-us/ef/core/change-tracking/identity-resolution - Pinelopi Kouleri

6

当我想要更新数据时,我遇到了类似的错误,我发现我可以通过清除属性上下文来修复它。这是我所做的。虽然不是同样的问题,但是由于出现了相同的错误,我认为可以用同样的方法来解决它。清除上下文似乎是一个好的解决方案,因为它是导致出现问题的原因。

context.ChangeTracker.Clear();

context.Cliente.Update(cliente);

context.SaveChanges();

如果你需要这个,你可能也遭受了糟糕的上下文生命周期管理,正如手头问题的原因所在。 - Gert Arnold
问题在于对于初学者来说,跟随教程进行生命周期管理并不是一件易于实现的事情。 - Joe

2

当我使用Entity Framework复制数据库中的某些记录并更改一个作为其他实体键的列时,遇到了同样的问题。

跟踪模式的更改没有解决这个问题。

通过在EntityTypeConfiguration中正确设置主键来解决了该问题,以包含此处描述的更改后的值x.EntityTwoKey。

builder.HasKey(x => new { x.EntityOneKey, x.EntityTwoKey });

1
在我的情况下,当我在两个IF语句块内运行SaveChanges两次时,就会出现这个错误。我将SaveChanges移动到那两个代码块之外。只是一个小提示,在我的服务层中,它使用AsNoTracking()查询数据。
if (user.SendPaymentStatus)
{
    user.SendPaymentStatus = false;
    saveChanges = true;
    //_userService.SaveChanges(user, false);

    msg = GetPaymentHTML(user.MasterNodeName, user.Payee, DbMasterNode.LastPaidUtc);
    Framework.Email.SendEmail(email, "MasterNode Payment - " + user.MasterNodeName, msg);        
}

if (user.SendNodeStatus)
{
    user.SendNodeStatus = false;
    saveChanges = true;
    //_userService.SaveChanges(user, false);

    msg = GetStatusHTML(user.MasterNodeName, user.Payee, DbMasterNode.CurrentStatus, DbMasterNode.LastSeenUtc);
    Framework.Email.SendEmail(email, "MasterNode Down - " + user.MasterNodeName, msg);
}

if (saveChanges)
{
    user.SendPaymentStatus = false;
    _userService.SaveChanges(user, false);
}

0
我们最近在添加多个新项目时遇到了相同的问题,其中标识列id设置为0。我们正在使用OracleDataAccess客户端用于EF Core 3,当我们执行saveChanges()时,我们为新实体设置序列号,但是如果已经有另一个id = 0的项,则在尝试添加()时会出错。
我们所做的修复措施是确保标识列的配置正确:
1.) 设置关键字
builder.HasKey(t => t.Id);

2.) 正确设置数据库生成选项

[Column("ID"), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public virtual int Id { get; set; }

或者流利的等价物:

builder.Property(t => t.Id)
            .ValueGeneratedOnAdd();

我们没有正确进行第二步,并且将其设置为DatabaseGeneratedOption.None,然后EF Core在添加时失败了。

0
在我的情况下,上述问题在我将主键列Id设置为标识列后得到了解决。

0

在尝试更新数值时,我遇到了同样的问题。然后我发现问题出在这里。

services.AddDbContext<StudentContext>(option => option.UseSqlServer(Configuration.GetConnectionString("databasename")),ServiceLifetime.Singleton);

然后我移除了lifetime,这对我来说很有效。

services.AddDbContext<StudentContext>(option => option.UseSqlServer(Configuration.GetConnectionString("databasename")));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接