ObjectStateManager 中已存在具有相同键的对象。 ObjectStateManager 无法跟踪具有相同键的多个对象

Posted

技术标签:

【中文标题】ObjectStateManager 中已存在具有相同键的对象。 ObjectStateManager 无法跟踪具有相同键的多个对象【英文标题】:An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key 【发布时间】:2012-09-17 03:08:14 【问题描述】:

使用带有通用存储库模式的 EF5 和 ninject 进行依赖注入,并在尝试使用我的 edmx 存储的过程将实体更新到数据库时遇到问题。

我在 DbContextRepository.cs 中的更新是:

public override void Update(T entity)

    if (entity == null)
        throw new ArgumentException("Cannot add a null entity.");

    var entry = _context.Entry<T>(entity);

    if (entry.State == EntityState.Detached)
    
        _context.Set<T>().Attach(entity);
        entry.State = EntityState.Modified;
    

从我的 AddressService.cs 中返回到我的存储库:

 public int Save(vw_address address)

    if (address.address_pk == 0)
    
        _repo.Insert(address);
    
    else
    
        _repo.Update(address);
    

    _repo.SaveChanges();

    return address.address_pk;

当它点击 Attach 和 EntityState.Modified 时,它会出现错误:

我查看了堆栈和 Internet 上的许多建议,但没有提出任何解决它的方法。任何变通方法将不胜感激。

谢谢!

【问题讨论】:

【参考方案1】:

编辑:原始答案使用Find 而不是Local.SingleOrDefault。它与@Juan 的Save 方法结合使用,但它可能导致对数据库的不必要查询,并且else 部分可能从未执行(执行else 部分会导致异常,因为Find 已经查询了数据库并且没有找到实体所以它无法更新)。感谢@BenSwayne 发现问题。

您必须检查具有相同键的实体是否已被上下文跟踪并修改该实体而不是附加当前实体:

public override void Update(T entity) where T : IEntity 
    if (entity == null) 
        throw new ArgumentException("Cannot add a null entity.");
    

    var entry = _context.Entry<T>(entity);

    if (entry.State == EntityState.Detached) 
        var set = _context.Set<T>();
        T attachedEntity = set.Local.SingleOrDefault(e => e.Id == entity.Id);  // You need to have access to key

        if (attachedEntity != null) 
            var attachedEntry = _context.Entry(attachedEntity);
            attachedEntry.CurrentValues.SetValues(entity);
         else 
            entry.State = EntityState.Modified; // This should attach entity
        
    
  

如您所见,主要问题是SingleOrDefault 方法需要知道找到实体的密钥。您可以创建简单的接口来公开密钥(在我的示例中为IEntity)并在您想要以这种方式处理的所有实体中实现它。

【讨论】:

谢谢。所以我用 int Id get; 创建了一个接口 IEntity放;然后尝试做 public override void Update(T entity) where T : IEntity 但它不喜欢 where T : IEntity。这是在存储库类中,即公共类 DbContextRepository : BaseRepository where T : class 如果这有所作为。谢谢! 在这种情况下,将约束直接放在类定义上 嗯.. 仍然没有太多运气。我想知道是不是因为我使用的是 edmx 模型。但是我无法将约束直接放在类上,因为它实现了 BaseRepository 和 IRepository。此外,在 edmx 中,实体来自视图,主键类似于 address_pk。 我有similar problem(仍然没有解决)。问题是不更新的虚拟引用类型变量属性。 @LadislavMrnka:当您使用 set.Find() 时,如果它不在对象状态管理器中,它将从数据库中加载,对吗?那么在上面的代码中attachedEntity 将永远不为空并且你永远不会附加传入的实体? (即:你永远不会到达else 声明)也许我误解了DbSet&lt;&gt;.Find() 的文档。我们不应该使用 DbSet.Local 吗?【参考方案2】:

我不想通过添加接口或属性来污染我自动生成的 EF 类。所以这真的是上述一些答案的一点点(所以归功于Ladislav Mrnka)。这为我提供了一个简单的解决方案。

我在更新方法中添加了一个函数来找到实体的整数键。

public void Update(TEntity entity, Func<TEntity, int> getKey)

    if (entity == null) 
        throw new ArgumentException("Cannot add a null entity.");
    

    var entry = _context.Entry<T>(entity);

    if (entry.State == EntityState.Detached) 
        var set = _context.Set<T>();
        T attachedEntity = set.Find.(getKey(entity)); 

        if (attachedEntity != null) 
            var attachedEntry = _context.Entry(attachedEntity);
            attachedEntry.CurrentValues.SetValues(entity);
         else 
            entry.State = EntityState.Modified; // This should attach entity
        
    
  

然后当你调用你的代码时,你可以使用..

repository.Update(entity, key => key.myId);

【讨论】:

您不应该使用set.Local.Find 而不是set.Find 吗?我相信您的代码将始终访问数据库,因此永远不会使 attachedEntity 变量为空。 msdn.microsoft.com/en-us/library/jj592872(v=vs.113).aspx【参考方案3】:

您实际上可以通过反射来检索 Id,请参见下面的示例:

        var entry = _dbContext.Entry<T>(entity);

        // Retreive the Id through reflection
        var pkey = _dbset.Create().GetType().GetProperty("Id").GetValue(entity);

        if (entry.State == EntityState.Detached)
        
            var set = _dbContext.Set<T>();
            T attachedEntity = set.Find(pkey);  // access the key
            if (attachedEntity != null)
            
                var attachedEntry = _dbContext.Entry(attachedEntity);
                attachedEntry.CurrentValues.SetValues(entity);
            
            else
            
                entry.State = EntityState.Modified; // attach the entity
            
        

【讨论】:

@SerjSagan 你可以简单地做_dbContext.Set&lt;T&gt;().Create().GetTy..【参考方案4】:

@serj-sagan你应该这样做:

**请注意,YourDb 应该是从 DbContext 派生的类。

public abstract class YourRepoBase<T> where T : class

    private YourDb _dbContext;
    private readonly DbSet<T> _dbset;

    public virtual void Update(T entity)
    
        var entry = _dbContext.Entry<T>(entity);

        // Retreive the Id through reflection
        var pkey = _dbset.Create().GetType().GetProperty("Id").GetValue(entity);

        if (entry.State == EntityState.Detached)
        
           var set = _dbContext.Set<T>();
           T attachedEntity = set.Find(pkey);  // access the key
           if (attachedEntity != null)
           
               var attachedEntry = _dbContext.Entry(attachedEntity);
               attachedEntry.CurrentValues.SetValues(entity);
           
           else
           
              entry.State = EntityState.Modified; // attach the entity
           
       
    

【讨论】:

【参考方案5】:

如果您将上下文设置为 AsNoTracking(),这将停止 aspmvc 跟踪对内存中实体的更改(这正是您在网络上想要的)。

_dbContext.Products.AsNoTracking().Find(id);  

我建议您在 http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/advanced-entity-framework-scenarios-for-an-mvc-web-application 阅读更多相关信息

【讨论】:

【参考方案6】:

另一种解决方案(基于@Sergey 的回答)可能是:

private void Update<T>(T entity, Func<T, bool> predicate) where T : class

    var entry = Context.Entry(entity);
    if (entry.State == EntityState.Detached)
    
        var set = Context.Set<T>();
        T attachedEntity = set.Local.SingleOrDefault(predicate); 
        if (attachedEntity != null)
        
            var attachedEntry = Context.Entry(attachedEntity);
            attachedEntry.CurrentValues.SetValues(entity);
        
        else
        
            entry.State = EntityState.Modified; // This should attach entity
        
    

然后你会这样称呼它:

Update(EntitytoUpdate, key => key.Id == id)

【讨论】:

【参考方案7】:

如果不使用反射并且不想使用接口,可以使用函数委托在数据库中查找实体。 这是上面的更新示例。

private void Update<T>(T entity, Func<ObservableCollection<T>, T> locatorMap) where T : class

    var entry = Context.Entry(entity);
    if (entry.State == EntityState.Detached)
    
        var set = Context.Set<T>();
        T attachedEntity = locatorMap(set.Local); 

        if (attachedEntity != null)
        
            var attachedEntry = Context.Entry(attachedEntity);
            attachedEntry.CurrentValues.SetValues(entity);
        
        else
        
            entry.State = EntityState.Modified; // This should attach entity
        
    

你可以这样称呼它:

Update(EntitytoUpdate, p => p.SingleOrDefault(a => a.Id == id))

【讨论】:

【参考方案8】:

分离找到的实体(参见Ladislav's 解决方案中的attachedEntity)并重新附加修改后的实体对我来说效果很好。

这背后的原因很简单:如果某些东西是不可变的,那么将它(作为一个整体,实体)从它所属的位置替换为所需的。

以下是如何执行此操作的示例:

var set = this.Set<T>();
if (this.Entry(entity).State == EntityState.Detached)

    var attached = set.Find(id);
    if (attached != null)  this.Entry(attached).State = EntityState.Detached; 
    this.Attach(entity);


set.Update(entity);

当然,人们可能很容易发现这个 sn-p 是泛型方法的一部分,因此使用了T,它是一个模板参数,以及Set&lt;T&gt;()

【讨论】:

【参考方案9】:

上面的答案可能是 EF 4.1+。对于那些在 4.0 上的人,试试这个简单的方法……没有经过真正的测试,但确实附加并保存了我的更改。

    public void UpdateRiskInsight(RiskInsight item)
    
        if (item == null)
        
            throw new ArgumentException("Cannot add a null entity.");
        

        if (item.RiskInsightID == Guid.Empty)
        
            _db.RiskInsights.AddObject(item);
        
        else
        
            item.EntityKey = new System.Data.EntityKey("GRC9Entities.RiskInsights", "RiskInsightID", item.RiskInsightID);
            var entry = _db.GetObjectByKey(item.EntityKey) as RiskInsight;
            if (entry != null)
            
                _db.ApplyCurrentValues<RiskInsight>("GRC9Entities.RiskInsights", item);
            

        

        _db.SaveChanges();

    

【讨论】:

您似乎完全错过了 OP 希望使用通用存储库执行此操作的事实。【参考方案10】:

您可能忘记在 algun 位置 instaçia 对象 fBLL = new FornecedorBLL();

【讨论】:

以上是关于ObjectStateManager 中已存在具有相同键的对象。 ObjectStateManager 无法跟踪具有相同键的多个对象的主要内容,如果未能解决你的问题,请参考以下文章

ObjectStateManager 中已存在具有相同键的对象。 ObjectStateManager 无法跟踪具有相同键的多个对象

ObjectStateManager 中已存在具有相同键的对象。 ObjectStateManager 无法跟踪具有相同键的多个对象

ObjectStateManager 中已存在具有同一键的对象。ObjectStateManager 无法跟踪具有相同键的多个对象

无法附加分离的实体:“ObjectStateManager 中已存在具有相同键的对象”

c# ef 修改提示,ObjectStateManager 中已存在具有同一键的对象。

“ObjectStateManager 中已存在具有相同键的对象...”将实体状态设置为已修改时引发异常