繁体   English   中英

将对象图重新附加到EntityContext:“无法使用相同的键跟踪多个对象”

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM