2011-11-01 96 views
27

我有一个应用程序,您可以在其中创建新类型的产品并添加到该产品的某些成分。产品和成分都是保存在数据库中的实体。产品实体具有一组成分实体。实体4.1用新子实体更新现有父实体

(简体版)

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。然而,在状态改变I的情况下,得到异常“ObjectStateManager中已存在具有相同密钥的对象”。

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

后“一些”寻找我想通了,问题是,所有的成分具有0的主键(因为它们没有添加),当您更改父实体的状态(产品),所有的子实体(成分)都与关键字0相关联,这导致问题,因为关键字不再是唯一的。

我一直在寻找解决方案,但无法弄清楚如何解决这个问题。在尝试更改状态之前,我尝试将其添加到上下文中,但是产品和配料之间的链接缺失......如何使用新的尚未添加的子实体更新现有的父实体?

我使用Entity Framework 4.1和Code First。

希望你能帮助我!

回答

36

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

好吧,这是一种暴力攻击来更新子集合。 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(); 
} 
+0

这个绝对完美的作品!多谢! – DirkDooms

+0

花了很长时间才找到如何正确更新实体。感谢context.Entry(productInDb).CurrentValues.SetValues(product); –

+0

这真是太好了,现在我只需要弄清楚如何通过反射使它工作,这样我就可以遍历我正在保存的实体上的所有集合。 – Nikkoli

-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();     

      } 

     } 
+0

在我看来,你并没有考虑删除orderDetails –

4

我的GraphDiff扩展的DbContext发现这个最近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小组使用他的代码#864 Provide better support for working with disconnected entities

-1

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

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