C# ObjectStateManager 中已存在具有相同键的对象。ObjectStateManager 无法跟踪具有相同键的多个对象
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/12585664/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key
提问by Juan
Using EF5 with a generic Repository Pattern and ninject for dependency injenction and running into an issue when trying to update an entity to the database utilizing stored procs with my edmx.
使用具有通用存储库模式的 EF5 和 ninject 进行依赖注入,并在尝试使用存储过程将实体更新到数据库时遇到问题和我的 edmx。
my update in DbContextRepository.cs is:
我在 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;
}
}
From my AddressService.cs which goes back to my repository I have:
从我的 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;
}
When it hits the Attach and EntityState.Modified it pukes with the error:
当它遇到 Attach 和 EntityState.Modified 时,它会因错误而呕吐:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
ObjectStateManager 中已存在具有相同键的对象。ObjectStateManager 无法跟踪具有相同键的多个对象。
I have looked through many of the suggestions in stack and on the Internet and not coming up with anything that resolves it. Any work arounds would be appreciated.
我查看了堆栈和互联网上的许多建议,但没有提出任何解决方案。任何解决方法将不胜感激。
Thanks!
谢谢!
采纳答案by Ladislav Mrnka
Edit: Original answer used Findinstead of Local.SingleOrDefault. It worked in combination with @Juan's Savemethod but it could cause unnecessary queries to database and elsepart was probably never executed (executing the else part would cause exception because Find already queried the database and hadn't found the entity so it could not be updated). Thanks to @BenSwayne for finding the issue.
编辑:使用原始答案Find代替Local.SingleOrDefault. 它与@Juan 的Save方法结合使用,但可能会导致对数据库进行不必要的查询,并且else部分可能永远不会执行(执行 else 部分会导致异常,因为 Find 已经查询了数据库并且没有找到实体,因此无法更新) . 感谢@BenSwayne 发现问题。
You must check if an entity with the same key is already tracked by the context and modify that entity instead of attaching the current one:
您必须检查具有相同键的实体是否已被上下文跟踪并修改该实体而不是附加当前实体:
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
}
}
}
As you can see the main issue is that SingleOrDefaultmethod needs to know the key to find the entity. You can create simple interface exposing the key (IEntityin my example) and implement it in all your entities you want to process this way.
如您所见,主要问题是该SingleOrDefault方法需要知道找到实体的键。您可以创建公开密钥的简单接口(IEntity在我的示例中),并在您想要以这种方式处理的所有实体中实现它。
回答by thomas gathings
That answer above may be EF 4.1+. For those on 4.0, try this simple method...not really tested but did attach and save my changes.
上面的答案可能是 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();
}
回答by Toffee
You can actually retreive the Id through reflection, see example below:
您实际上可以通过反射检索 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
}
}
回答by Toffee
@serj-saganyou should do it in this way:
@ serj-sagan你应该这样做:
**Note that YourDb should be a class derived from DbContext.
**请注意,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
}
}
}
}
}
回答by Sergey
Without reflection and if you don't want to use interfaces, you can use functional delegates to find an entity in the database. Here is the updated sample from above.
没有反射,如果你不想使用接口,你可以使用函数委托来查找数据库中的实体。这是上面的更新示例。
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
}
}
}
You would call it like this:
你会这样称呼它:
Update(EntitytoUpdate, p => p.SingleOrDefault(a => a.Id == id))
回答by Geoff Wells
I didn't want to pollute my auto generated EF classes by adding interfaces, or attributes. so this is really a little bit from some of the above answers (so credit goes to Ladislav Mrnka). This provided a simple solution for me.
我不想通过添加接口或属性来污染我自动生成的 EF 类。所以这真的是上面一些答案的一点点(所以归功于 Ladislav Mrnka)。这为我提供了一个简单的解决方案。
I added a func to the update method that found the integer key of the entity.
我在 update 方法中添加了一个 func 来找到实体的整数键。
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
}
}
}
Then when you call your code, you can use..
然后当你调用你的代码时,你可以使用..
repository.Update(entity, key => key.myId);
回答by Yashvit
If you set your context to AsNoTracking() this will stop aspmvc tracking the changes to the entity in memory (which is what you want anyway on the web).
如果您将上下文设置为 AsNoTracking(),这将停止 aspmvc 跟踪内存中实体的更改(这正是您在网络上想要的)。
_dbContext.Products.AsNoTracking().Find(id);
I would recommend you read more about this at http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/advanced-entity-framework-scenarios-for-an-mvc-web-application
我建议您在http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/advanced-entity-framework-scenarios-for-an-mvc-web阅读更多相关信息-应用
回答by Andrés S.
Another solution (based on @Sergey's answer) could be:
另一个解决方案(基于@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
}
}
}
And then you would call it like this:
然后你会这样称呼它:
Update(EntitytoUpdate, key => key.Id == id)
回答by Junior Ramoty
You may have forgotten to insta?ia the object fBLL = new FornecedorBLL();in algun place
您可能忘记fBLL = new FornecedorBLL();在 algun place安装对象
回答by Alexander Christov
Detaching the entity found (see attachedEntityin Ladislav'ssolution) and re-attaching the modified one worked for me just fine.
卸下实体发现(见attachedEntity的拉吉斯拉夫的解决方案),并重新安装修改后的一个工作对我来说只是罚款。
The reasoning behind this is simple: if something's immutable then replace it (as a whole, entity) from where it belongs with the desired one.
这背后的原因很简单:如果某些东西是不可变的,那么将它(作为一个整体,实体)从它所属的地方替换为所需的那个。
Here's an example of how to do this:
以下是如何执行此操作的示例:
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);
Of course, one may easily figure out that this snippet is part of a generic method, hence the use of T, which is a template parameter, and Set<T>().
当然,人们可能很容易发现这个片段是泛型方法的一部分,因此使用了T,它是一个模板参数,而Set<T>().

