Подтвердить что ты не робот

Объект 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. Однако при изменении состояния я получается исключение " Объект с тем же ключом уже существует в ObjectStateManager".

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

После "некоторых" поисков я понял, что проблема в том, что все ингредиенты имеют первичный ключ 0 (поскольку они еще не добавлены), и когда вы меняете состояние родительского объекта (продукта), все дочерние объекты (компоненты) привязаны к контексту с ключом 0, что вызывает проблему, поскольку ключи больше не уникальны.

Я искал решение, но не могу понять, как решить эту проблему. Я попытался добавить ингредиенты в контекст перед изменением состояния, но тогда отсутствует связь между продуктом и ингредиентами... Как обновить существующий родительский объект с новыми, еще не добавленными дочерними объектами?

Я использую Entity Framework 4.1 и Code First.

Надеюсь, ты поможешь мне!

4b9b3361

Ответ 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();
}

Ответ 2

Я нашел эту недавнюю статью на GraphDiff для DbContext.

По-видимому, это универсальный вариант многократного использования Slauma .

Пример кода:

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 Обеспечить лучшую поддержку для работы с отключенными объектами.

Ответ 3

Я считаю, это более простое решение.

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

Ответ 4

после многих месяцев борьбы с пониманием всей этой дерьмовой платформы Entity Framework я надеюсь, что это может помочь кому-то и не пройти через какое-либо разочарование, которое я пережил.

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

            }

        }