【问题标题】:Entity 4.1 Updating an existing parent entity with new child Entities实体 4.1 用新的子实体更新现有的父实体
【发布时间】:2011-12-19 14:17:38
【问题描述】:

我有一个应用程序,您可以在其中创建一种新型产品并向该产品添加一些成分。产品和成分都是保存在数据库中的实体。产品实体具有成分实体的集合。

(简化版)

public class Product
   Public Sub New()
     Me.Ingredients = New List(Of Ingredient)()
   End Sub

   Property Ingredients as ICollection(Of Ingredient)
end class

当我第一次保存产品时,一切都很顺利:我只需将它添加到上下文并调用 SaveChanges。

myDataContext.Products.Add(product)
myDataContext.SaveChanges()

产品(父)和成分(子)都被保存并相互链接。一切都很好。

但是,当我在现有产品中添加/删除成分时,我开始遇到问题。我首先清除产品实体中现有的成分集合,然后再次添加更新的成分列表(我不重复使用成分添加时刻)。然后我将产品实体的状态更改为已修改并调用 savechanges。但是,在状态更改时,我得到异常“ObjectStateManager 中已存在具有相同键的对象”。

myDataContext.Entry(product).State = EntityState.Modified

经过“一些”搜索后,我发现问题在于所有成分的主键均为 0(因为它们尚未添加),当您更改父实体(产品)的状态时,所有子实体实体(成分)使用键 0 附加到上下文,这会导致问题,因为键不再唯一。

我一直在寻找解决方案,但不知道如何解决这个问题。我尝试在更改状态之前将成分添加到上下文中,但是缺少产品和成分之间的链接...如何使用新的尚未添加的子实体更新现有父实体?

我使用 Entity Framework 4.1 和 Code First。

希望你能帮助我!

【问题讨论】:

    标签: entity-framework-4.1


    【解决方案1】:

    我先清除产品中已有的成分集合 实体,然后再次添加更新的成分列表。

    嗯,这是一种蛮力攻击来更新子集合。 EF 没有任何魔法来更新孩子——这意味着:添加新的孩子,删除删除的孩子,更新现有的孩子——只需将父母的状态设置为Modified。基本上,这个过程会迫使您也从数据库中删除旧的孩子并插入新的孩子,如下所示:

    // product is the detached product with the detached new children collection
    using (var context = new MyContext())
    {
        var productInDb = context.Products.Include(p => p.Ingredients)
            .Single(p => p.Id == product.Id);
    
        // Update scalar/complex properties of parent
        context.Entry(productInDb).CurrentValues.SetValues(product);
    
        foreach (var ingredient in productInDb.Ingredients.ToList())
            context.Ingredients.Remove(ingredient);
    
        productInDb.Ingredients.Clear(); // not necessary probably
    
        foreach (var ingredient in product.Ingredients)
            productInDb.Ingredients.Add(ingredient);
    
        context.SaveChanges();
    }
    

    更好的方法是更新内存中的子集合而不删除数据库中的所有子集合:

    // product is the detached product with the detached new children collection
    using (var context = new MyContext())
    {
        var productInDb = context.Products.Include(p => p.Ingredients)
            .Single(p => p.Id == product.Id);
    
        // Update scalar/complex properties of parent
        context.Entry(productInDb).CurrentValues.SetValues(product);
    
        var ingredientsInDb = productInDb.Ingredients.ToList();
        foreach (var ingredientInDb in ingredientsInDb)
        {
            // Is the ingredient still there?
            var ingredient = product.Ingredients
                .SingleOrDefault(i => i.Id == ingredientInDb.Id);
    
            if (ingredient != null)
                // Yes: Update scalar/complex properties of child
                context.Entry(ingredientInDb).CurrentValues.SetValues(ingredient);
            else
                // No: Delete it
                context.Ingredients.Remove(ingredientInDb);
        }
    
        foreach (var ingredient in product.Ingredients)
        {
            // Is the child NOT in DB?
            if (!ingredientsInDb.Any(i => i.Id == ingredient.Id))
                // Yes: Add it as a new child
                productInDb.Ingredients.Add(ingredient);
        }
    
        context.SaveChanges();
    }
    

    【讨论】:

    • 我花了很长时间才找到如何正确更新实体。感谢 context.Entry(productInDb).CurrentValues.SetValues(product);
    • 这太好了,现在我只需要弄清楚如何通过反射使其工作,这样我就可以遍历我正在保存的实体上的所有集合。
    • 这让我想哭。
    • 为什么要这么复杂?
    • 感谢这个 Slauma!
    【解决方案2】:

    在努力理解整个蹩脚的实体框架数月之后,我希望这可以帮助某人,而不是经历我所忍受的任何挫折。

    public void SaveOrder(SaleOrder order)
            {
                using (var ctx = new CompanyContext())
                {
                    foreach (var orderDetail in order.SaleOrderDetails)
                    {
                        if(orderDetail.SaleOrderDetailId == default(int))
                        {
                            orderDetail.SaleOrderId = order.SaleOrderId;
                            ctx.SaleOrderDetails.Add(orderDetail);
                        }else
                        {
                            ctx.Entry(orderDetail).State = EntityState.Modified;
                        }
                    }
    
                    ctx.Entry(order).State = order.SaleOrderId == default(int) ? EntityState.Added : EntityState.Modified;
                    ctx.SaveChanges();                
    
                }
    
            }
    

    【讨论】:

    • 在我看来你没有考虑删除 orderDetails
    【解决方案3】:

    我在 DbContext 的 GraphDiff 扩展上找到了这个最近的 article

    显然它是Slaumasolution 的通用、可重复使用的变体。

    示例代码:

    using (var context = new TestDbContext())
    {
        // Update DBcompany and the collection the company and state that the company 'owns' the collection Contacts.
        context.UpdateGraph(company, map => map.OwnedCollection(p => p.Contacts));     
        context.SaveChanges();
    }
    

    附带说明;我看到作者已经向 EF 团队提议使用他在 issue #864 Provide better support for working with disconnected entities 中的代码。

    【讨论】:

      【解决方案4】:

      我认为,这是更简单的解决方案。

      public Individual
      {
      .....
      
      public List<Address> Addresses{get;set;}
      
      
      }
      
      //where base.Update from Generic Repository
      public virtual void Update(T entity)
              {
                  _dbset.Attach(entity);
                  _dataContext.Entry(entity).State = EntityState.Modified;
              }
      
      //overridden update
       public override void Update(Individual entity)
              {
      
      
                  var entry = this.DataContext.Entry(entity);
                  var key = Helper.GetPrimaryKey(entry);
                  var dbEntry = this.DataContext.Set<Individual>().Find(key);
      
                  if (entry.State == EntityState.Detached)
                  {
                      if (dbEntry != null)
                      {
                          var attachedEntry = this.DataContext.Entry(dbEntry);
                          attachedEntry.CurrentValues.SetValues(entity);
                      }
                      else
                      {
                          base.Update(entity);
                      }
                  }
                  else
                  {
                      base.Update(entity);
                  }
                  if (entity.Addresses.Count > 0)
                  {
                      foreach (var address in entity.Addresses)
                      {
                          if (address != null)
                          {
                              this.DataContext.Set<Address>().Attach(address);
                              DataContext.Entry(address).State = EntityState.Modified;
                          }
                      }
                  }
              }
      

      【讨论】:

        猜你喜欢
        • 2019-12-05
        • 1970-01-01
        • 2013-07-24
        • 2021-02-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-11-22
        相关资源
        最近更新 更多