web-dev-qa-db-fra.com

Comment supprimer des entités enfant avant parent avec Entity Framework CF?

J'essaie d'utiliser le code EF en premier pour supprimer un enregistrement de base de données (deleteMe) et ses enfants (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();

Toutefois, Entity Framework semble incapable de suivre le fait que les enregistrements enfants doivent être supprimés avant le parent. Je reçois l'erreur:

L'instruction DELETE était en conflit avec la contrainte REFERENCE "ItemPrice_Item".
Le conflit s'est produit dans la base de données "DEVDB", table "dbo.ItemPrices", colonne 'Item_ItemId'.
La déclaration a été terminée.

Comment pourrais-je exécuter cette suppression dans EF?

21
quakkels

J'ai fini par trouver une ligne rapide qui le ferait pour moi:

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

EF6

context.Children.RemoveRange(parent.Children)
10
Eng. Samer T

La suppression en cascade dans EF dépend de la suppression en cascade configurée dans la base de données. Par conséquent, si vous n'avez pas configuré la suppression en cascade dans la base de données, vous devez d'abord charger tous les prix d'articles dans votre application et les marquer comme supprimés.

8
Ladislav Mrnka

La solution la plus simple consiste à parcourir d'abord les prix et les changements de sauvegarde des appels, puis à définir l'entrée sur delete pour deleteMe et à nouveau les modifications de sauvegarde, mais avez-vous vérifié ceci: Le code de structure d'entité supprime d'abord avec cascade ? Cela semble être ce que vous voulez.

Curieux, mais aussi pourquoi vous ne supprimez pas les entités du contexte à supprimer, mais définissez plutôt l'état d'entrée?

Une autre option consiste à définir la suppression en cascade http://blogs.msdn.com/b/alexj/archive/2009/08/19/tip-33-how-cascade-delete-really-works-in-ef.aspx

Faites quelque chose comme ça (pas testé mais j'espère que vous obtenez le 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();
}     

De plus, vous pouvez appeler:

db.Prices.Remove(item);

et 

db.DeleteMes.Remove(deleteMe);

au lieu de définir l'état d'entrée. Pas sûr s'il y a une différence dans les coulisses entre les deux cependant.

5
SventoryMang

La suppression en cascade dans la structure Entity est une opération délicate, car vous devez être sûr du graphe d'objet entité à supprimer. Il est préférable de toujours écrire un test d'intégration pour ces suppressions en cascade. 

Si vous essayez de supprimer l'entité parent dans EF, il essaiera d'exécuter des instructions de suppression pour toutes les entités enfants du dbcontext en cours. En conséquence, il ne initialisera aucune entité enfant qui n’a pas été chargée. Cela entraînerait une erreur d’exécution du SGBDR, qui violerait la contrainte de clé étrangère. Pour être sûr, assurez-vous que toutes les entités dépendantes sont chargées dans dbcontext avant la suppression.

3
marvelTracker

Si votre objet fait référence à vous-même, vous pouvez supprimer les enfants plusieurs à plusieurs et un à plusieurs en utilisant la méthode ci-dessous. Rappelez-vous simplement d'appeler db.SaveChanges () après :)

[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);
    }
}
0
Matthew Hudson

J'avais un problème similaire et pour moi, il semblait que je n'avais pas correctement établi la relation entre parent et enfant dans leurs classes respectives.

Mon correctif consistait à ajouter les attributs spécifiés ci-dessous à la classe Child, pour la propriété qui représentait son ID parent

    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
Artie Leech