[英]An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key
[英]Reattaching an object graph to an EntityContext: “cannot track multiple objects with the same key”
EF真的可以这么糟糕吗? 也许...
假设我有一个完全加载的,断开连接的对象图,如下所示:
myReport =
{Report}
{ReportEdit {User: "JohnDoe"}}
{ReportEdit {User: "JohnDoe"}}
基本上是由同一用户完成2次修改的报告。
然后我这样做:
EntityContext.Attach(myReport);
InvalidOperationException :ObjectStateManager中已经存在具有相同键的对象。 ObjectStateManager无法使用相同的键跟踪多个对象。
为什么? 因为EF尝试将{User: "JohnDoe"}
实体附加到TWICE。
这将有效:
myReport =
{Report}
{ReportEdit {User: "JohnDoe"}}
EntityContext.Attach(myReport);
这里没有问题,因为{User: "JohnDoe"}
实体仅在对象图中出现一次。
而且,由于您无法控制EF附加实体的方式,因此无法阻止EF附加整个对象图。 因此,如果您要重新附加一个包含多个对同一实体的引用的复杂实体,那么真是好运。
至少我就是这样。 任何意见?
更新 :添加了示例代码:
// Load the report
Report theReport;
using (var context1 = new TestEntities())
{
context1.Reports.MergeOption = MergeOption.NoTracking;
theReport = (from r in context1.Reports.Include("ReportEdits.User")
where r.Id == reportId
select r).First();
}
// theReport looks like this:
// {Report[Id=1]}
// {ReportEdit[Id=1] {User[Id=1,Name="John Doe"]}
// {ReportEdit[Id=2] {User[Id=1,Name="John Doe"]}
// Try to re-attach the report object graph
using (var context2 = new TestEntities())
{
context2.Attach(theReport); // InvalidOperationException
}
问题是您修改了默认的MergeOption
:
context1.Reports.MergeOption = MergeOption.NoTracking;
使用NoTracking
检索的实体仅供只读使用,因为没有修正。 这在MergeOption
的文档中。 由于您设置了NoTracking
,因此您现在有了{User: "JohnDoe"}
两个完全独立的副本; 没有修正,“重复的”引用不会被简化为单个实例。
现在,当您尝试保存{User: "JohnDoe"}
“两个”副本时,第一个成功添加到上下文中,但是由于密钥冲突而无法添加第二个。
在再次阅读EF文档(v4的东西-比3.5的东西更好)并阅读了这篇文章之后 ,我意识到了这个问题-并解决了。
EF使用MergeOption.NoTracking
创建对象图,其中每个实体引用都是该实体的不同实例 。 因此,在我的示例中,两个ReportEdits上的两个User引用都是不同的对象-即使它们的所有属性都相同。 它们都处于Detached状态,并且都具有具有相同值的EntityKey。
问题在于,当在ObjectContext上使用Attach方法时,上下文基于每个User实例是独立的实例的事实重新附加每个User实例- 它忽略了它们具有相同EntityKey的事实 。
我想这种行为是有道理的。 如果实体处于分离状态,则EF不知道两个引用之一是否已被修改,以此类推。因此,我们假设InvalidOperationException不是假定它们都不变并且将它们视为相等。
但是,如果像我的情况一样,您知道处于分离状态的两个User引用实际上是相同的,并希望在重新连接它们时将它们视为相等,该怎么办? 事实证明,解决方案非常简单: 如果在图中多次引用一个实体,则这些引用中的每个引用都必须指向object的单个实例 。
使用IEntityWithRelationships
,我们可以遍历分离的对象图并更新引用,并将重复的引用合并到同一实体实例。 然后,ObjectContext将把所有重复的实体引用视为同一实体,并重新附加它,而不会发生任何错误。
粗略地基于我上面引用的博客文章,我创建了一个类来合并对重复实体的引用,以便它们共享相同的对象引用。 请记住,如果在处于分离状态时修改了任何重复引用,您将得到不可预测的结果:在图中找到的第一个实体始终优先。 但是,在特定情况下,它似乎可以解决问题。
public class EntityReferenceManager
{
///
/// A mapping of the first entity found with a given key.
///
private Dictionary _entityMap;
///
/// Entities that have been searched already, to limit recursion.
///
private List _processedEntities;
///
/// Recursively searches through the relationships on an entity
/// and looks for duplicate entities based on their EntityKey.
///
/// If a duplicate entity is found, it is replaced by the first
/// existing entity of the same key (regardless of where it is found
/// in the object graph).
///
///
public void ConsolidateDuplicateRefences(IEntityWithRelationships ewr)
{
_entityMap = new Dictionary();
_processedEntities = new List();
ConsolidateDuplicateReferences(ewr, 0);
_entityMap = null;
_processedEntities = null;
}
private void ConsolidateDuplicateReferences(IEntityWithRelationships ewr, int level)
{
// Prevent unlimited recursion
if (_processedEntities.Contains(ewr))
{
return;
}
_processedEntities.Add(ewr);
foreach (var end in ewr.RelationshipManager.GetAllRelatedEnds())
{
if (end is IEnumerable)
{
// The end is a collection of entities
var endEnum = (IEnumerable)end;
foreach (var endValue in endEnum)
{
if (endValue is IEntityWithKey)
{
var entity = (IEntityWithKey)endValue;
// Check if an object with the same key exists elsewhere in the graph
if (_entityMap.ContainsKey(entity.EntityKey))
{
// Check if the object reference differs from the existing entity
if (_entityMap[entity.EntityKey] != entity)
{
// Two objects with the same key in an EntityCollection - I don't think it's possible to fix this...
// But can it actually occur in the first place?
throw new NotSupportedException("Cannot handle duplicate entities in a collection");
}
}
else
{
// First entity with this key in the graph
_entityMap.Add(entity.EntityKey, entity);
}
}
if (endValue is IEntityWithRelationships)
{
// Recursively process relationships on this entity
ConsolidateDuplicateReferences((IEntityWithRelationships)endValue, level + 1);
}
}
}
else if (end is EntityReference)
{
// The end is a reference to a single entity
var endRef = (EntityReference)end;
var pValue = endRef.GetType().GetProperty("Value");
var endValue = pValue.GetValue(endRef, null);
if (endValue is IEntityWithKey)
{
var entity = (IEntityWithKey)endValue;
// Check if an object with the same key exists elsewhere in the graph
if (_entityMap.ContainsKey(entity.EntityKey))
{
// Check if the object reference differs from the existing entity
if (_entityMap[entity.EntityKey] != entity)
{
// Update the reference to the existing entity object
pValue.SetValue(endRef, _entityMap[endRef.EntityKey], null);
}
}
else
{
// First entity with this key in the graph
_entityMap.Add(entity.EntityKey, entity);
}
}
if (endValue is IEntityWithRelationships)
{
// Recursively process relationships on this entity
ConsolidateDuplicateReferences((IEntityWithRelationships)endValue, level + 1);
}
}
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.