2013-05-09 13 views
13

Ho una tabella con un riferimento automatico in cui ParentId è un FK per l'ID (PK).
Utilizzando EF (codice-prima), ho creato il mio rapporto come segue:L'istruzione DELETE è in conflitto con il vincolo SAME TABLE REFERENCE con Entity Framework

this.HasOptional(t => t.ParentValue) 
    .WithMany(t => t.ChildValues) 
    .HasForeignKey(t => t.ParentId); 

Quando cerco di eliminare i bambini ei suoi genitori, i comandi di cancellazione questioni EF al database non sono nella ordine mi aspettavo che andassero - tenta di cancellare prima il record genitore.

mi rendo conto che ho un paio di opzioni qui (nessuno dei quali mi piace):

  1. record Elimina bambino prima, fare un salvataggio completo/commit, e quindi eliminare record padre. Con la complessità del mio modello e la logica che lo mantiene, questa non è un'opzione - non posso emettere più comandi di commit quando voglio.
  2. Sciogliere la relazione prima di eliminare qualsiasi cosa. Questa sembra una soluzione più sensata, ma, ancora una volta, devo emettere un commit separato con un'istruzione UPDATE prima dei DELETE. Voglio evitare più chiamate di salvataggio/commit.
  3. Utilizzare un trigger per eliminare i figli prima di eliminare il record padre. Ma mi piacerebbe evitare i trigger e la loro natura problematica il più possibile.

Quindi la domanda è .. c'è un modo per far rispettare la cancellazione dei bambini prima del record padre? Forse mi manca una sorta di modo esplicito di dire a EF che ha bisogno di prendersi cura di questi bambini prima del genitore? Forse c'è un modo per dirigere EF da eliminare in ordine decrescente di ID? Non lo so .. pensieri?

+1

Potete utilizzare un'eliminazione a cascata qui? Se si utilizzano le migrazioni prima del codice, è possibile impostare cascade = true quando viene creata la tabella, altrimenti potrebbe essere necessario aggiornarlo tramite un'altra migrazione o tramite il database. Penserei che EF lo gestisse se fosse in grado di effettuare una cascata. –

+0

Buona idea. I nostri DBA non vogliono abilitare la cascata per l'intero DB, ma potremmo essere in grado di farlo solo su questo riferimento FK. È quello che stavo discutendo con un DBA. :) Anche se sto ancora cercando altri suggerimenti/idee forse nell'ambito del codice relativo a EF. – Kon

+0

Aggiornamento: impossibile aggiungere la cascata all'eliminazione su un FK all'interno di un riferimento personale in una tabella, a causa dell'errore del ciclo. – Kon

risposta

12

Eliminare genitore e figlio come il seguente funziona per me. I bambini vengono cancellati prima il genitore ed è un singolo andata e ritorno di database (una chiamata a SaveChanges) con ovviamente tre istruzioni DELETE in una singola transazione:

using (var ctx = new MyContext()) 
{ 
    var parent = ctx.MyEntities.Include(e => e.Children).FirstOrDefault(); 

    foreach (var child in parent.Children.ToList()) 
     ctx.MyEntities.Remove(child); 

    ctx.MyEntities.Remove(parent); 

    ctx.SaveChanges(); 
} 

(Usando ToList() è necessario qui perché chiamando Remove per i bambini rimuove anche dalla collezione del genitore Children. Senza usare ToList un'eccezione di runtime sarebbe gettato che la raccolta del ciclo foreach si iterazione di è stato modificato.)

l'ordine in cui Remove è chiamato per i bambini e il genitore non ha importanza. Funziona anche:

using (var ctx = new MyContext()) 
{ 
    var parent = ctx.MyEntities.Include(e => e.Children).FirstOrDefault(); 

    var children = parent.Children.ToList(); 

    ctx.MyEntities.Remove(parent); 

    foreach (var child in children) 
     ctx.MyEntities.Remove(child); 

    ctx.SaveChanges(); 
} 

EF ordina le istruzioni DELETE nell'ordine corretto in entrambi i casi.

Programma di test completo (EF 5/.NET 4.5/SQL Server):

using System.Collections.Generic; 
using System.Data.Entity; 
using System.Linq; 

namespace EFSelfReference 
{ 
    public class MyEntity 
    { 
     public int Id { get; set; } 
     public string Name { get; set; } 

     public int? ParentId { get; set; } 
     public MyEntity Parent { get; set; } 

     public ICollection<MyEntity> Children { get; set; } 
    } 

    public class MyContext : DbContext 
    { 
     public DbSet<MyEntity> MyEntities { get; set; } 

     protected override void OnModelCreating(DbModelBuilder modelBuilder) 
     { 
      modelBuilder.Entity<MyEntity>() 
       .HasOptional(e => e.Parent) 
       .WithMany(e => e.Children) 
       .HasForeignKey(e => e.ParentId); 
     } 
    } 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      Database.SetInitializer<MyContext>(
       new DropCreateDatabaseAlways<MyContext>()); 
      using (var ctx = new MyContext()) 
      { 
       ctx.Database.Initialize(false); 

       var parent = new MyEntity { Name = "Parent", 
        Children = new List<MyEntity>() }; 

       parent.Children.Add(new MyEntity { Name = "Child 1" }); 
       parent.Children.Add(new MyEntity { Name = "Child 2" }); 

       ctx.MyEntities.Add(parent); 

       ctx.SaveChanges(); 
      } 

      using (var ctx = new MyContext()) 
      { 
       var parent = ctx.MyEntities.Include(e => e.Children) 
        .FirstOrDefault(); 

       foreach (var child in parent.Children.ToList()) 
        ctx.MyEntities.Remove(child); 

       ctx.MyEntities.Remove(parent); 

       ctx.SaveChanges(); 
      } 
     } 
    } 
} 

Schermata dopo la prima using blocco con contenuto attuale della tabella DB prima che le entità vengono eliminati:

screen 1

Screenshot da SQL Profiler dopo l'ultimo SaveChanges:

screen 2

Ie Child 1 (Id = 2) e Child 2 (Id = 3) vengono eliminati prima delloParent (Id = 1).

+0

cosa succede se non voglio cancellare il genitore? Voglio solo cancellare il bambino? – eugenekgn

1

C'è un altro modo, (pensate agli svantaggi prima di farlo ...) potete impostare la relazione su ON DELETE CASCADE e provare a cancellare solo la riga genitore.

+4

Non è possibile eliminare la cascata in un vincolo SAME TABLE REFERENCE, come descritto nel titolo della domanda. –

16

Mi rendo conto che la risposta è di un anno, ma la trovo incompleta. Nella mia mente, una tabella autoreferenziale viene usata per rappresentare una profondità arbitraria.

Ad esempio, si consideri la seguente struttura:

/* 
* earth 
*  europe 
*   germany 
*   ireland 
*    belfast 
*    dublin 
*  south america 
*   brazil 
*    rio de janeiro 
*   chile 
*   argentina     
*    
*/ 

La risposta non risolve come eliminare la terra, o in Europa, dalla struttura di cui sopra.

In alternativa, invio il seguente codice (modifica della risposta fornita da Slauma, che ha svolto un buon lavoro tra l'altro).

Nella classe myContext, aggiungere i seguenti metodi:

public void DeleteMyEntity(MyEntity entity) 
{ 
    var target = MyEntities 
     .Include(x => x.Children) 
     .FirstOrDefault(x => x.Id == entity.Id); 

    RecursiveDelete(target); 

    SaveChanges(); 

} 

private void RecursiveDelete(MyEntity parent) 
{ 
    if (parent.Children != null) 
    { 
     var children = MyEntities 
      .Include(x => x.Children) 
      .Where(x => x.ParentId == parent.Id); 

     foreach (var child in children) 
     { 
      RecursiveDelete(child); 
     } 
    } 

    MyEntities.Remove(parent); 
} 

ho popolano i dati utilizzando il codice-prima con la seguente classe:

public class TestObjectGraph 
{ 
    public MyEntity RootEntity() 
    { 
     var root = new MyEntity 
     { 
      Name = "Earth", 
      Children = 
       new List<MyEntity> 
        { 
         new MyEntity 
         { 
          Name = "Europe", 
          Children = 
           new List<MyEntity> 
           { 
            new MyEntity {Name = "Germany"}, 
            new MyEntity 
            { 
             Name = "Ireland", 
             Children = 
              new List<MyEntity> 
              { 
               new MyEntity {Name = "Dublin"}, 
               new MyEntity {Name = "Belfast"} 
              } 
            } 
           } 
         }, 
         new MyEntity 
         { 
          Name = "South America", 
          Children = 
           new List<MyEntity> 
           { 
            new MyEntity 
            { 
             Name = "Brazil", 
             Children = new List<MyEntity> 
             { 
              new MyEntity {Name = "Rio de Janeiro"} 
             } 
            }, 
            new MyEntity {Name = "Chile"}, 
            new MyEntity {Name = "Argentina"} 
           } 
         } 
        } 
     }; 

     return root; 
    } 
} 

che li risparmio al mio database con il seguente codice:

ctx.MyEntities.Add(new TestObjectGraph().RootEntity()); 

quindi richiamare l'elimina in questo modo:

using (var ctx = new MyContext()) 
{ 
    var parent = ctx.MyEntities 
     .Include(e => e.Children) 
     .FirstOrDefault(); 

    var deleteme = parent.Children.First(); 

    ctx.DeleteMyEntity(deleteme); 
} 

che si traduce nel mio database ora avere una struttura in questo modo:

/* 
* earth 
*  south america 
*   brazil 
*    rio de janeiro 
*   chile 
*   argentina     
*    
*/ 

in cui l'Europa e tutti i suoi figli vengono eliminati.

in quanto sopra, sto specificando il primo figlio del nodo radice, per dimostrare che usando il mio codice è possibile cancellare in modo ricorsivo un nodo e tutti i suoi figli da qualsiasi punto della gerarchia.

se si desidera verificare l'eliminazione di everyting, si può semplicemente modificare la riga in questo modo:

ctx.DeleteMyEntity(parent); 

o qualsiasi nodo che si desidera nella struttura.

ovviamente, non otterrò la taglia, ma spero che il mio post aiuti qualcuno a cercare una soluzione che funzioni per entità autoreferenziali di profondità arbitraria.

Ecco il sorgente completo, che è una versione modificata del codice di Slauma dalla risposta selezionata:

using System; 
using System.Collections.Generic; 
using System.Data.Entity; 
using System.Linq; 

namespace EFSelfReference 
{ 
    public class MyEntity 
    { 
     public int Id { get; set; } 
     public string Name { get; set; } 

     public int? ParentId { get; set; } 
     public MyEntity Parent { get; set; } 

     public ICollection<MyEntity> Children { get; set; } 
    } 

    public class MyContext : DbContext 
    { 
     public DbSet<MyEntity> MyEntities { get; set; } 

     protected override void OnModelCreating(DbModelBuilder modelBuilder) 
     { 
      modelBuilder.Entity<MyEntity>() 
       .HasOptional(e => e.Parent) 
       .WithMany(e => e.Children) 
       .HasForeignKey(e => e.ParentId); 
     } 


     public void DeleteMyEntity(MyEntity entity) 
     { 
      var target = MyEntities 
       .Include(x => x.Children) 
       .FirstOrDefault(x => x.Id == entity.Id); 

      RecursiveDelete(target); 

      SaveChanges(); 

     } 

     private void RecursiveDelete(MyEntity parent) 
     { 
      if (parent.Children != null) 
      { 
       var children = MyEntities 
        .Include(x => x.Children) 
        .Where(x => x.ParentId == parent.Id); 

       foreach (var child in children) 
       { 
        RecursiveDelete(child); 
       } 
      } 

      MyEntities.Remove(parent); 
     } 
    } 

    public class TestObjectGraph 
    { 
     public MyEntity RootEntity() 
     { 
      var root = new MyEntity 
      { 
       Name = "Earth", 
       Children = 
        new List<MyEntity> 
        { 
         new MyEntity 
         { 
          Name = "Europe", 
          Children = 
           new List<MyEntity> 
           { 
            new MyEntity {Name = "Germany"}, 
            new MyEntity 
            { 
             Name = "Ireland", 
             Children = 
              new List<MyEntity> 
              { 
               new MyEntity {Name = "Dublin"}, 
               new MyEntity {Name = "Belfast"} 
              } 
            } 
           } 
         }, 
         new MyEntity 
         { 
          Name = "South America", 
          Children = 
           new List<MyEntity> 
           { 
            new MyEntity 
            { 
             Name = "Brazil", 
             Children = new List<MyEntity> 
             { 
              new MyEntity {Name = "Rio de Janeiro"} 
             } 
            }, 
            new MyEntity {Name = "Chile"}, 
            new MyEntity {Name = "Argentina"} 
           } 
         } 
        } 
      }; 

      return root; 
     } 
    } 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      Database.SetInitializer<MyContext>(
       new DropCreateDatabaseAlways<MyContext>()); 
      using (var ctx = new MyContext()) 
      { 
       ctx.Database.Initialize(false); 

       ctx.MyEntities.Add(new TestObjectGraph().RootEntity()); 
       ctx.SaveChanges(); 
      } 

      using (var ctx = new MyContext()) 
      { 
       var parent = ctx.MyEntities 
        .Include(e => e.Children) 
        .FirstOrDefault(); 

       var deleteme = parent.Children.First(); 

       ctx.DeleteMyEntity(deleteme); 
      } 

      Console.WriteLine("Completed...."); 
      Console.WriteLine("Press any key to exit"); 
      Console.ReadKey(); 
     } 
    } 
} 
Problemi correlati