8

Ich habe eine .NET4.0-Anwendung mit Entity Framework 5.0 e Sql Server CE 4.0.Löschen Eltern mit Kindern in einer Beziehung Beziehung

Ich habe zwei Entitäten mit einer Eins zu viele (Eltern/Kind) Beziehung. Ich habe es so konfiguriert, dass es bei der Elterentfernung kaskadiert wird, aber aus irgendeinem Grund scheint es nicht zu funktionieren.

Hier ist eine vereinfachte Version meiner Entitäten:

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

    } 

Die Verbindungszeichenfolge:

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

Und eine vereinfachte Version der Workflow meiner 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(); 
} 

Wenn ich versuche, Um die übergeordnete Entität zu löschen, wird Folgendes ausgelöst:

Die Beziehung konnte nicht geändert werden, da eine oder mehrere der Fremdschlüsseleigenschaften nicht nullfähig sind. Wenn eine Änderung an einer -Beziehung vorgenommen wird, wird die zugehörige Fremdschlüsseleigenschaft auf einen Nullwert festgelegt. Wenn der Fremdschlüssel keine Nullwerte unterstützt, muss eine neue Beziehung definiert werden, der Fremdschlüsseleigenschaft muss ein anderer Wert ungleich null zugewiesen werden, oder das nicht verwandte Objekt muss gelöscht werden.

Ich glaube, meine Beziehung Konfiguration ist in Ordnung (followed the documentation). Ich suchte auch nach guidelines on deleting detached entities.

Ich kann wirklich nicht verstehen, warum das Löschen nicht funktioniert. Ich möchte vermeiden, alle Kinder zu laden, sie einzeln zu löschen und sie das Elternteil zu löschen, weil es eine bessere Lösung als das geben muss.

Antwort

12

Das Festlegen des Status einer Entität auf Deleted und das Aufrufen von DbSet<T>.Remove für diese Entität sind nicht identisch.

Der Unterschied besteht darin, dass der Staat nur die Einstellung der Zustand der Root-Entität ändert (die Sie in context.Entry Pass) zu Deleted aber nicht den Zustand der verbundenen Einrichtungen während Remove dies tut, wenn die Beziehung mit Cascading löschen konfiguriert ist.

Wenn Sie eine Ausnahme erhalten, hängt tatsächlich davon ab, ob die Kinder (alle oder nur ein Teil) an den Kontext gebunden sind oder nicht. Dies führt zu einem Verhalten, das etwas schwer zu folgen:

  • Wenn Sie rufen Remove Sie keine Ausnahme erhalten, egal ob Kinder geladen werden oder nicht.Es gibt immer noch einen Unterschied:
    • Wenn die Kinder auf den Kontext gebunden sind, wird EF erzeugen eine DELETE Erklärung für jedes Kind angebracht, dann für die Eltern (weil Remove sie tat markieren alle als Deleted)
    • Wenn die Kinder sind nicht an den Kontext gebunden EF sendet nur eine DELETE-Anweisung für das übergeordnete Element an die Datenbank, und da kaskadierendes Löschen aktiviert ist, löscht die Datenbank auch die untergeordneten Elemente.
  • Wenn Sie den Zustand der Root-Entität zu Deleted festlegen können Sie möglicherweise eine Ausnahme erhalten:
    • Wenn Kinder auf den Kontext gebunden ihr Zustand nicht an Deleted und EF gesetzt werden beschweren Sie, dass Sie versuchen, einen Principal (die Root-Entität) in einer erforderlichen Beziehung zu löschen, ohne die abhängigen (die untergeordneten) oder zumindest ohne ihre Fremdschlüssel auf eine andere Root-Entität zu setzen, die sich nicht im Deleted-Status befindet. Das ist die Ausnahme, die Sie hatten: account ist die Wurzel und user1 ist abhängig von account und context.Entry(account).State = EntityState.Deleted; Aufruf wird auch user1 im Zustand Unchanged dem Kontext anhängen (oder Erkennung ändern in SaveChanges wird es tun, ich bin nicht sicher, dass stoßen). user1 ist Teil der account.Users-Auflistung, weil die Beziehung fixup die Sammlung in Ihrem ersten Kontext hinzugefügt hat, obwohl Sie sie nicht explizit in Ihrem Code hinzugefügt haben.
    • Wenn an die Kontexteinstellung keine untergeordneten Elemente angehängt sind, wird der Status des Stammverzeichnisses an Deleted eine DELETE-Anweisung an die Datenbank senden und erneutes kaskadierendes Löschen in der Datenbank löscht auch die untergeordneten Elemente. Dies funktioniert ohne Ausnahme. Ihr Code würde dann beispielsweise funktionieren, wenn Sie account.Users = null setzen, bevor Sie den Status im zweiten Kontext oder vor dem Eintritt in den zweiten Kontext auf Deleted setzen.

Meiner Meinung nach Remove mit ...

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

... ist eindeutig die bevorzugte Art und Weise, weil das Verhalten von Remove ist viel mehr, wie Sie für eine erforderliche Beziehung mit Cascading erwarten löschen (was in Ihrem Modell der Fall ist). Die Abhängigkeit des Verhaltens einer manuellen Statusänderung von Zuständen anderer Entitäten macht die Verwendung schwieriger. Ich würde es als fortgeschrittene Nutzung nur für Spezialfälle betrachten.

Der Unterschied ist nicht allgemein bekannt oder dokumentiert. Ich habe sehr wenige Beiträge darüber gesehen. Der einzige, den ich jetzt wieder finden konnte, ist this one by Zeeshan Hirani.

+1

Sehr aufschlussreich, @Slauma! Ich habe viel nach einem Hinweis gesucht, bevor ich die Frage gepostet habe, aber ich habe den von Ihnen erwähnten Beitrag nicht gefunden. Danke –

+0

Es scheint, dass viele Leute so viele Probleme haben, wenn sie versuchen, Eltern-Kind-Operationen in EF zu arbeiten, wie Einfügen, Aktualisieren und Löschen, jedes Mal wenn ich mehr über EF lese, desto enttäuschter. –

1

Ich habe einen etwas anderen Ansatz versucht und seltsamerweise hat es funktioniert. Wenn ich diesen Code ersetzen:

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

Durch diese:

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

Es funktioniert ohne weitere Probleme. Ich bin mir nicht sicher, ob das ein Fehler ist oder ob mir etwas fehlt. Ich würde mich über etwas Licht in dieser Angelegenheit freuen, da ich mir ziemlich sicher war, dass der erste Weg (EntityState.Deleted) der empfohlene Weg war.

Verwandte Themen