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

时间:2018-06-22 12:09:11

标签: c# entity-framework-core

我知道已经提出了这样的问题,但是解决方案并没有帮助我。

[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
  };

8 个答案:

答案 0 :(得分:8)

EF核心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;
    }
}

答案 1 :(得分:4)

Alexandar的答案是完全禁用跟踪,它解决了我的问题,但是我感到担忧,因为我不知道这会对我的其余应用程序产生什么影响。所以我去了Microsoft文档,发现this

如果要操纵实体实例并使用SaveChanges()将这些更改持久保存到数据库中,则应该禁用更改跟踪。

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

所以对我来说,解决方案是仅在需要时禁用跟踪。因此,我在代码的另一部分(从数据库中检索相同的条目)中使用了此代码,从而解决了我的问题:

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

答案 2 :(得分:3)

我遇到的许多问题都有一个令人讨厌的根源。 简而言之:我已经了解了为什么dbContext是作用域而不是单例的艰难方法。这是商店类型,但问题是相同的。 这是简化的测试初始化​​代码

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 ;
}

我重读了错误,最后注意到了主词

  

无法跟踪实体类型“商店”的实例,因为已经跟踪了另一个具有相同键值的{'Id'}实例

因此必须有一些孤立的实例来阻止我使用商店。我没有保存对s1s2的任何引用,因此即使离开声明和初始化的范围,也必须storeContext将引用存储在插入的对象上。这就是为什么我无法正常更新变量,也是为什么我从db对象中“查询”的所有导航属性都被分配了(延迟加载与此无关)。以下代码解决了我所有的问题。

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应该受作用域限制的众多原因之一。 感谢hint

答案 3 :(得分:1)

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

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

基于 Bryan 给出的解决方案。我想我使用较新版本的 EF/Automapping。这对我有用。

答案 4 :(得分:0)

当我通过Entity Framework复制数据库中的某些记录并更改作为其他人的实体键的一列时,我遇到了同样的问题。 跟踪模式更改无法解决问题。

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

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

答案 5 :(得分:0)

就我而言,在两个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);
}

答案 6 :(得分:0)

尝试更新值时遇到相同的问题。然后我发现了我正在使用的问题。

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

然后我删除了一生,这对我来说很好。

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

答案 7 :(得分:0)

就我而言,在我将主键列 Id 设置为标识列后,上述问题已解决。