【问题标题】:Reattaching an object graph to an EntityContext: "cannot track multiple objects with the same key"将对象图重新附加到 EntityContext:“无法使用相同的键跟踪多个对象”
【发布时间】:2011-02-21 11:52:07
【问题描述】:

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 附加实体的方式,因此无法阻止它附加整个对象图。所以真的,如果你想重新附加一个包含多个对同一实体的引用的复杂实体......好吧,祝你好运。

至少在我看来是这样。有cmets吗?

更新:添加示例代码:


// 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
}

【问题讨论】:

  • 取决于您如何创建图表,但您没有显示。我不认为这是一个错误,但我无法确定您发布的内容。
  • 添加了一些示例代码 - 这是否说明了问题?

标签: .net entity-framework .net-3.5 ado.net


【解决方案1】:

问题是你修改了默认的MergeOption

context1.Reports.MergeOption = MergeOption.NoTracking;

使用NoTracking 检索的实体仅供只读使用,因为没有修复;这是在MergeOption 的文档中。因为您设置了NoTracking,所以您现在拥有两个完全独立的{User: "JohnDoe"} 副本;如果不进行修复,“重复”引用不会归结为单个实例。

现在,当您尝试保存 {User: "JohnDoe"} 的“两个”副本时,第一个副本成功添加到上下文中,但由于密钥违规,第二个副本无法添加。

【讨论】:

    【解决方案2】:

    在再次阅读 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);
                    }
                }
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2011-08-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多