Как удалить дочерние объекты перед родителями с Entity Framework CF? - PullRequest
22 голосов
/ 08 марта 2012

Я пытаюсь использовать EF code-first для удаления записи БД (deleteMe) и ее дочерних элементов (deleteMe.Prices).

foreach (var deleteMe in deleteThese)
{ 
   // Delete validation
   if(CanDeleteItem(deleteMe.ItemId))
   {
      db.Entry(deleteMe).State = EntityState.Deleted;

      foreach (var item in deleteMe.Prices)
      {
         db.Entry(item).State = EntityState.Deleted; // cascade delete
      }
   }
}
db.SaveChanges();

Однако Entity Framework, похоже, не может отследитьтот факт, что дочерние записи должны быть удалены перед родителем.Я получаю ошибку:

Оператор DELETE конфликтует с ограничением REFERENCE "ItemPrice_Item".
Конфликт произошел в базе данных "DEVDB", таблице "dbo.ItemPrices", столбце "Item_ItemId".
Оператор был прерван.

Как мне выполнить это удаление в EF?

Ответы [ 7 ]

34 голосов
/ 20 марта 2012

Я закончил тем, что нашел быструю линию, которая сделала бы это для меня:

foreach (var deleteMe in deleteThese)
{ 
   // Delete validation
   if(CanDeleteItem(deleteMe.ItemId))
   {
      ///
      deleteMe.Prices.ToList().ForEach(p => db.ItemPrices.Remove(p));
      ///

      db.Entry(deleteMe).State = EntityState.Deleted;
   }
}
db.SaveChanges();
11 голосов
/ 24 декабря 2016

EF6

context.Children.RemoveRange(parent.Children)
8 голосов
/ 08 марта 2012

Каскадное удаление в EF зависит от каскадного удаления, настроенного относительно в базе данных, поэтому, если у вас не настроено каскадное удаление в базе данных, вы должны сначала загрузить все цены товаров в свое приложение и отметить их как удаленные.

5 голосов
/ 08 марта 2012

Что ж, самое простое решение - это сначала пройтись по ценам и сохранить изменения вызовов, затем установить запись для delete для deleteMe и снова сохранить изменения вызовов, но вы проверили это: каскад ? Кажется, это то, что вы хотите.

Интересно, почему вы просто не удаляете сущности из контекста для удаления, а устанавливаете состояние входа?

Другой вариант - установить каскадное удаление http://blogs.msdn.com/b/alexj/archive/2009/08/19/tip-33-how-cascade-delete-really-works-in-ef.aspx

Сделайте что-то подобное (не проверено, но, надеюсь, вы получите jist):

using (TransactionScope scope = new TransactionScope())
{    
    foreach (var deleteMe in deleteThese)
    { 
   // Delete validation
      if(CanDeleteItem(deleteMe.ItemId))
      {

         foreach (var item in deleteMe.Prices)
         {
            db.Entry(item).State = EntityState.Deleted; // cascade delete
         }
         db.SaveChanges();

         db.Entry(deleteMe).State = EntityState.Deleted;


     }
   }
   db.SaveChanges();
   scope.Complete();
}     

Дополнительно вы можете позвонить:

db.Prices.Remove(item);

и

db.DeleteMes.Remove(deleteMe);

вместо установки состояния входа. Не уверен, что между этими двумя сценами есть разница.

3 голосов
/ 09 марта 2012

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

Если вы попытаетесь удалить родительский объект в EF, он попытается выполнить операторы удаления для любых дочерних объектов в текущем dbcontext.В результате он не будет инициализировать дочерние объекты, которые не были загружены.Это приведет к ошибке времени выполнения СУБД, которая нарушит ограничение внешнего ключа.Чтобы быть в безопасности, перед удалением убедитесь, что все зависимые объекты загружены в текущий dbcontext.

0 голосов
/ 16 октября 2018

У меня была похожая проблема, и для меня это выглядело так, как будто я неправильно установил отношения между Родителем и Ребенком в их соответствующих классах.

Я решил добавить атрибуты, указанные ниже, к Ребенкукласс, для свойства, которое представляет его идентификатор родителя

    public class Child
    {
        [Key, Column(Order = 1)]
        public string Id { get; set; }

        [Key, ForeignKey("Parent"), Column(Order = 2)]  // adding this line fixed things for me
        public string ParentId {get; set;}
    }

    public class Parent
    {
        [Key, Column(Order = 1)]
        public string Id { get; set; }

        ...

        public virtual ICollection<Child> Children{ get; set; }
    }
0 голосов
/ 15 августа 2016

Если ваш объект ссылается на себя, вы можете удалить как детей со многими, так и детей со многими, используя метод, описанный ниже.Просто не забудьте вызвать db.SaveChanges () впоследствии:)

[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
    Object obj = this.db.Objects.Find(id);
    this.DeleteObjectAndChildren(obj);
    this.db.Objects.Remove(obj);
    this.db.SaveChanges();
    return this.Json(new { success = true });
}

/// <summary>
/// This deletes an object and all children, but does not commit changes to the db.
///  - MH @ 2016/08/15 14:42
/// </summary>
/// <param name="parent">
/// The object.
/// </param>
private void DeleteObjectAndChildren(Object parent)
{
    // Deletes One-to-Many Children
    if (parent.Things != null && parent.Things.Count > 0)
    {
        this.db.Things.RemoveRange(parent.Things);
    }

    // Deletes Self Referenced Children
    if (parent.Children != null && parent.Children.Count > 0)
    {
        foreach (var child in parent.Children)
        {
            this.DeleteObjectAndChildren(child);
        }

        this.db.Objects.RemoveRange(parent.Children);
    }
}
...