ObjectStateManager中已经存在具有相同键的对象。 ObjectStateManager不能使用同一个键跟踪多个对象

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

当它打到附件和EntityState.Modified它pukes与错误:

ObjectStateManager中已经存在具有相同键的对象。 ObjectStateManager不能使用同一个键跟踪多个对象。

我已经浏览了堆栈和互联网上的许多build议,而不是提出任何可以解决的问题。 任何工作周围将不胜感激。

谢谢!

编辑 :使用原始的答案Find而不是Local.SingleOrDefault 。 它与@ Juan的Save方法结合使用,但是可能会导致对数据库的不必要的查询,或者else部分可能从未执行过(执行else部分会导致exception,因为Find已经查询过数据库并且没有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方法需要知道find这个实体的关键。 您可以创build一个简单的接口公开键(在我的例子中是IEntity ),并在您想要以这种方式处理的所有实体中实现它。

我不想通过添加接口或属性来污染自动生成的EF类。 所以从以上的一些答案中可以看出这一点(所以请注意Ladislav Mrnka)。 这为我提供了一个简单的解决scheme。

我添加了一个func到find实体的整数键的更新方法。

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

实际上你可以通过reflection来检索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 } } 

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

}

另一个解决scheme(基于@谢尔盖的答案)可能是:

 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) 

没有反思,如果你不想使用接口,你可以使用function委托在数据库中find一个实体。 这里是从上面更新的示例。

 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)) 

如果你将你的上下文设置为AsNoTracking(),这将停止aspmvc跟踪内存中实体的变化(这正是你想要的任何东西)。

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

我build议你阅读更多关于这个在http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/advanced-entity-framework-scenarios-for-an-mvc-web -应用

分离find的实体(参见Ladislav解决scheme中的attachedEntity实体)并重新附加修改后的实体对我来说工作得很好。

这背后的原因很简单:如果某个东西是不可变的,那么用它所属的地方replace它(作为整体,实体)。

这是一个如何做到这一点的例子:

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

当然,可以很容易地知道这个片段是generics方法的一部分,因此使用T作为模板参数,使用Set<T>()

上面的答案可能是EF 4.1+。 对于那些在4.0,尝试这个简单的方法…没有真正的testing,但附加和保存我的变化。

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

您可能忘记instaçia对象fBLL = new FornecedorBLL(); 在algun的地方