当将实体状态设置为修改时,如果在ObjectStateManager中已经存在具有相同键的对象,则会抛出"在ObjectStateManager中已经存在具有相同键的对象..."异常。
当将实体状态设置为修改时,如果在ObjectStateManager中已经存在具有相同键的对象,则会抛出"在ObjectStateManager中已经存在具有相同键的对象..."异常。
我根据一些例子(包括《Pro ASP.NET MVC 3》和《Professional ASP.NET MVC 3》等书籍)创建了一些简单的ASP.NET MVC 3应用程序,使用了EF 4.1(因为我对这些技术还不熟悉)。
我使用以下存储库(控制器的所有操作方法都使用它的单个实例)来访问数据库:
public class ProductRepository : IProductRepository { private readonly EFDbContext _context = new EFDbContext(); #region Implementation of IProductRepository .... public void SaveProduct(Product product) { if (product.ProductId == 0) { _context.Products.Add(product); } else { _context.Entry(product).State = EntityState.Modified; } _context.SaveChanges(); } .... }
这个存储库执行的是我使用的例子中所示的更新操作。
产品类:
public class Product { public int ProductId { get; set; } public string Name { get; set; } public string Description { get; set; } public decimal Price { get; set; } public string Category { get; set; } }
在更新产品时,我得到了异常“一个具有相同键的对象已经存在于ObjectStateManager中。ObjectStateManager无法跟踪具有相同键的多个对象”。
我知道类似的问题在这里已经讨论过,但我的问题有点不同:
为什么这段代码从例子中拿来的不起作用(尽管看起来非常简单和直接)?我可能做错了什么或者遗漏了什么。
出现这个问题的原因是在设置实体状态为"修改"时,ObjectStateManager中已经存在具有相同键的对象。解决方法是在更新实体之前,先检查是否已存在相同键的对象,并根据需要进行处理。
最近我遇到了同样的问题,并通过以下泛型方法解决了这个问题:
public TEntity Update(TEntity model, bool persist) { if (model == null) { throw new ArgumentException("Cannot update a null entity."); } var updateModel = Get(model.Id); if (updateModel == null) { return model; } this.context.Entry(updateModel).CurrentValues.SetValues(model); this.Save(persist); return model; }
在这个方法中,首先检查传入的实体是否为空,如果为空则抛出异常。然后通过传入的实体的Id从数据库中获取对应的实体。如果获取到的实体为空,则直接返回传入的实体,表示不需要更新。如果获取到了对应的实体,则使用ObjectStateManager的CurrentValues属性将传入的实体的值更新到获取到的实体中。最后调用Save方法将更改保存到数据库中,并返回传入的实体。
通过这个方法,我们可以避免出现"An object with the same key already exists in the ObjectStateManager..."异常,并成功更新实体的状态。
出现这个问题的原因是在保存项目时没有更新product.ProductId
。这意味着当您再次保存项目时,它会再次添加到上下文中,导致出现错误。
由于Id将由数据库添加(假设它是自动生成的Id),所以您需要将产品数据重新读取到客户端上。
解决方法是在保存项目时更新product.ProductId
。这样,当您再次保存项目时,上下文就不会将它再次添加到上下文中,从而避免出现错误。
以下是一个示例代码,展示了如何更新product.ProductId
:
// 保存项目时更新product.ProductId if (product.ProductId == 0) { // 保存项目前,先读取产品数据 var existingProduct = dbContext.Products.Find(product.Id); if (existingProduct != null) { product.ProductId = existingProduct.ProductId; } } dbContext.Entry(product).State = EntityState.Modified; dbContext.SaveChanges();
通过以上的代码,您可以确保在保存项目时更新product.ProductId
,从而避免出现"An object with the same key already exists in the ObjectStateManager..."异常。
"An object with the same key already exists in the ObjectStateManager..."异常是在设置实体状态为修改时抛出的。解决方法是从上下文中获取记录,并调用`_context.Entry(currentProduct).CurrentValues.SetValues(product);`来更新记录。
根据Ladislav Mrnka在Stackoverflow的回答,这似乎是一个不好的解决办法,他在这篇帖子中表示,EF会在内部存储对实体的请求,因此理想情况下,实体已经存在且不需要额外的数据库调用。
问题的根本原因似乎是一旦从上下文中获取了产品,上下文就会跟踪该产品,并且这就是导致所有问题的原因。因此,将更改合并回去是唯一的解决办法。
为了避免这种跟踪,可以使用`AsNoTracking()`方法。
参考链接:
- [An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key](https://stackoverflow.com/questions/5672255)
- [Entity Framework and Connection Pooling](https://stackoverflow.com/questions/3653009/3653392#3653392)
- [AsNoTracking()](http://msdn.microsoft.com/en-us/library/gg679352%28v=vs.103%29.aspx)