在再次阅读 EF 文档(v4 的东西 - 它比 3.5 的东西更好)并阅读 this post 之后,我意识到了这个问题 - 并解决了问题。
使用MergeOption.NoTracking,EF 创建一个对象图,其中每个实体引用都是实体的不同实例。因此,在我的示例中,2 个 ReportEdits 上的两个 User 引用都是不同的对象——即使它们的所有属性都是相同的。它们都处于 Detached 状态,并且它们的 EntityKeys 具有相同的值。
问题在于,当在 ObjectContext 上使用 Attach 方法时,上下文会根据它们是单独的实例这一事实重新附加每个 User 实例 - 它忽略了它们具有相同 EntityKey 的事实。
我想这种行为是有道理的。如果实体处于分离状态,则 EF 不知道两个引用之一是否已被修改,等等。因此,我们不会假设它们都未更改并将它们视为相同,而是会得到一个 InvalidOperationException。
但是,如果像我的情况一样,您知道处于分离状态的两个用户引用实际上是相同的,并且希望它们在重新附加时被视为平等?结果证明解决方案很简单:如果一个实体在图中被多次引用,那么每个引用都需要指向该对象的一个实例。
使用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);
}
}
}
}
}