После прочтения документации EF снова (материал v4 - это лучше, чем 3.5) и прочтения этого поста , я понял проблему - и обойти.
При MergeOption.NoTracking
EF создает граф объектов, где каждая ссылка на сущность является отдельным экземпляром сущности . Так что в моем примере обе пользовательские ссылки на 2 ReportEdits являются различными объектами - даже если все их свойства одинаковы. Они оба находятся в отдельном состоянии, и у них обоих есть EntityKeys с одинаковым значением.
Проблема в том, что при использовании метода Attach в ObjectContext контекст повторно присоединяет каждый экземпляр 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);
}
}
}
}
}