8

Ho un'applicazione .NET 4.0 con Entity Framework 5.0 e Sql Server CE 4.0.Elimina genitore con figli in relazione uno a molti

Ho due entità con una relazione da uno a molti (genitore/figlio). L'ho configurato per eliminare in cascata la rimozione dei genitori, ma per qualche motivo non sembra funzionare.

Ecco una versione semplificata dei miei soggetti:

public class Account 
    { 
     public int AccountKey { get; set; } 
     public string Name { get; set; } 

     public ICollection<User> Users { get; set; } 
    } 

    internal class AccountMap : EntityTypeConfiguration<Account> 
    { 
     public AccountMap() 
     { 
      this.HasKey(e => e.AccountKey); 
      this.Property(e => e.AccountKey).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); 
      this.Property(e => e.Name).IsRequired(); 
     } 
    } 


    public class User 
    { 
     public int UserKey { get; set; } 
     public string Name { get; set; } 

     public Account Account { get; set; } 
     public int AccountKey { get; set; } 
    } 

    internal class UserMap : EntityTypeConfiguration<User> 
    { 
     public UserMap() 
     { 
      this.HasKey(e => e.UserKey); 
      this.Property(e => e.UserKey).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); 
      this.Property(e => e.Name).IsRequired(); 


      this.HasRequired(e => e.Account) 
       .WithMany(e => e.Users) 
       .HasForeignKey(e => e.AccountKey); 
     } 
    } 

    public class TestContext : DbContext 
    { 
     public TestContext() 
     { 
      this.Configuration.LazyLoadingEnabled = false; 
     } 

     public DbSet<User> Users { get; set; } 
     public DbSet<Account> Accounts { get; set; } 

     protected override void OnModelCreating(DbModelBuilder modelBuilder) 
     { 
      modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); modelBuilder.Conventions.Remove<StoreGeneratedIdentityKeyConvention>(); 
      modelBuilder.LoadConfigurations(); 
     } 

    } 

La stringa di connessione:

<connectionStrings> 
    <add name="TestContext" connectionString="Data Source=|DataDirectory|\TestDb.sdf;" providerName="System.Data.SqlServerCe.4.0" /> 
    </connectionStrings> 

E una versione semplificata del flusso di lavoro di mio app:

static void Main(string[] args) 
{ 
    try 
    { 
     Database.SetInitializer(new DropCreateDatabaseAlways<TestContext>()); 
     using (var context = new TestContext()) 
      context.Database.Initialize(false); 

     Account account = null; 
     using (var context = new TestContext()) 
     { 
      var account1 = new Account() { Name = "Account1^" }; 
      var user1 = new User() { Name = "User1", Account = account1 }; 

      context.Accounts.Add(account1); 
      context.Users.Add(user1); 

      context.SaveChanges(); 

      account = account1; 
     } 

     using (var context = new TestContext()) 
     { 
      context.Entry(account).State = EntityState.Deleted; 
        context.SaveChanges(); 
     } 
    } 
    catch (Exception e) 
    { 
     Console.WriteLine(e.ToString()); 
    } 

    Console.WriteLine("\nPress any key to exit..."); 
    Console.ReadLine(); 
} 

Quando provo per eliminare l'entità genitore, genera:

La relazione non può essere modificata perché una o più delle proprietà di chiave esterna non è annullabile. Quando viene apportata una modifica a una relazione , la proprietà chiave esterna correlata viene impostata su un valore nullo. Se la chiave esterna non supporta valori null, è necessario definire una nuova relazione , la proprietà chiave esterna deve essere assegnata a un altro valore non null oppure l'oggetto non correlato deve essere eliminato.

Credo che la mia configurazione di relazione sia ok (followed the documentation). Ho anche cercato guidelines on deleting detached entities.

Non riesco davvero a capire perché quell'eliminazione non funzionerà. Voglio evitare di caricare tutti i bambini, cancellandoli uno ad uno e eliminando il genitore, perché ci deve essere una soluzione migliore di quella.

risposta

12

Impostare lo stato di un'entità su Deleted e chiamare DbSet<T>.Remove per questa entità non è la stessa.

La differenza è che l'impostazione dello stato cambia solo lo stato del soggetto principale (quello che si passa in context.Entry) per Deleted, ma non lo stato degli enti collegati, mentre Remove fa questo se il rapporto è configurato con cascata cancellare.

Se si ottiene un'eccezione in realtà dipende dai bambini (tutti o solo una parte) di essere collegati al contesto o meno. Questo porta ad un comportamento che è un po 'difficile da seguire:

  • Se si chiama Remove non si ottiene un'eccezione, non importa se i bambini vengono caricati o meno.C'è ancora una differenza:
    • Se i bambini sono attaccati al contesto, EF genererà una dichiarazione DELETE per ogni bambino attaccato, poi per il genitore (perché Remove fatto contrassegnarli tutti come Deleted)
    • Se il i bambini non sono collegati al contesto EF invierà solo un'istruzione DELETE per il genitore al database e poiché l'eliminazione a cascata è abilitata, il database eliminerà anche i figli.
  • Se si imposta lo stato del soggetto principale per Deleted è possibile possibilmente ottenere un'eccezione:
    • Se i bambini sono attaccati al contesto il loro stato non sarà impostata su Deleted e EF si lamenta che si sta tentando di eliminare un principal (l'entità root) in una relazione richiesta senza eliminare i dipendenti (i figli) o almeno senza impostare le loro chiavi esterne su un'altra entità root che non è nello stato Deleted. Questo è l'eccezione si ha: account è la radice e user1 è un dipendente di account e chiamando context.Entry(account).State = EntityState.Deleted; anche allegare user1 in stato Unchanged al contesto (o cambia il rilevamento in SaveChanges lo farà, non sono sicuro che intestano). user1 fa parte della raccolta account.Users perché la correzione delle relazioni l'ha aggiunta alla raccolta nel primo contesto sebbene non l'abbia aggiunta esplicitamente nel codice.
    • Se nessun bambino è collegato al contesto, lo stato della radice su Deleted invierà un'istruzione DELETE al database e di nuovo l'eliminazione a cascata nel database eliminerà anche i figli. Questo funziona senza eccezioni. Ad esempio, il codice funzionerà se si imposta account.Users = null prima di impostare lo stato su Deleted nel secondo contesto o prima di immettere il secondo contesto.

A mio parere utilizzando Remove ...

using (var context = new TestContext()) 
{ 
    context.Accounts.Attach(account); 
    context.Accounts.Remove(account); 
    context.SaveChanges(); 
} 

... è chiaramente il modo preferito, perché il comportamento di Remove è molto più come ci si aspetterebbe per un rapporto richiesto con cascata cancella (che è il caso nel tuo modello). La dipendenza del comportamento di una modifica di stato manuale su stati di altre entità rende più difficile l'utilizzo. Lo considererei un utilizzo avanzato solo per casi speciali.

La differenza non è ampiamente nota o documentata. Ho visto pochissimi post a riguardo. L'unico che ho potuto trovare adesso, è this one by Zeeshan Hirani.

+1

Molto illuminante, @Slauma! Ho cercato molto per qualche indizio prima di pubblicare la domanda, ma non avevo trovato il post che hai menzionato. Grazie –

+0

Sembra che molte persone stiano avendo così tanti problemi quando provano a far funzionare le operazioni genitore-figlio in EF come inserire, aggiornare ed eliminare ogni volta che leggo di più su EF, più mi delude. –

1

Ho provato un approccio leggermente diverso e, stranamente, ha funzionato. Se sostituisco questo codice:

using (var context = new TestContext()) 
{ 
    context.Entry(account).State = EntityState.Deleted; 
    context.SaveChanges(); 
} 

Con questo:

using (var context = new TestContext()) 
{ 
    context.Entry(account).State = EntityState.Unchanged; 
    context.Accounts.Remove(account); 
    context.SaveChanges(); 
} 

Funziona senza ulteriori problemi. Non sono sicuro se questo è un bug o se mi manca qualcosa. Gradirei davvero un po 'di luce sull'argomento, perché ero abbastanza sicuro che il primo modo (EntityState.Deleted) fosse quello raccomandato.

Problemi correlati