实体4.1使用新的子实体更新现有父实体

时间:2011-11-01 15:14:46

标签: entity-framework-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。

希望你能帮助我!

4 个答案:

答案 0 :(得分: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();
}

答案 1 :(得分:4)

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

显然,它是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

答案 2 :(得分:-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;
                    }
                }
            }
        }

答案 3 :(得分:-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();                

            }

        }