8

Mam aplikację .NET4.0 z Entity Framework 5.0 e Sql Server CE 4.0.Usuń rodzica z dziećmi w jednym do wielu relacji

Mam dwie jednostki o relacji jeden do wielu (rodzic/dziecko). Skonfigurowałem go do kaskadowego usuwania przy usuwaniu rodziców, ale z jakiegoś powodu nie działa.

Tutaj jest uproszczoną wersją mojego podmioty:

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

    } 

Ciąg połączenia:

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

I uproszczoną wersją mojego aplikacji workflow:

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

Kiedy próbuję aby usunąć jednostkę nadrzędną, rzuca:

Zależności nie można zmienić, ponieważ jedna lub więcej właściwości klucza obcy nie jest możliwa do zniesienia. W przypadku zmiany relacji na relację powiązana właściwość klucza obcego jest ustawiona na wartość pustą. Jeśli klucz obcy nie obsługuje wartości pustych, musi zostać zdefiniowana nowa relacja , właściwość klucza obcego musi mieć przypisaną inną wartość inną niż null lub niepowiązany obiekt musi zostać usunięty.

Wierzę, że moja konfiguracja związku jest w porządku (followed the documentation). Szukałem również guidelines on deleting detached entities.

Naprawdę nie mogę zrozumieć, dlaczego to usunięcie nie zadziała. Chcę uniknąć ładowania wszystkich dzieci, usuwając je jeden po drugim i usuwając rodzica, ponieważ musi istnieć lepsze rozwiązanie.

Odpowiedz

12

Ustawienie stanu jednostki na Deleted i wywołanie DbSet<T>.Remove dla tej jednostki nie są takie same.

Różnica polega na tym, że ustalenie stanu tylko zmienia stan podmiotu root (jedno z nich przechodzą do context.Entry) do Deleted ale nie stan podmiotami powiązanymi podczas Remove robi to jeśli związek jest skonfigurowany z kaskadowych usunąć.

Jeśli otrzymasz wyjątek, w rzeczywistości zależy to od tego, czy dzieci (wszystkie lub tylko część) są dołączone do kontekstu, czy nie. To prowadzi do zachowań, które jest nieco trudny do naśladowania:

  • Jeśli zadzwonisz Remove nie dostaniesz wyjątek, bez względu na to, czy dzieci są załadowane, czy nie.Nadal istnieje różnica:
    • Jeśli dzieci są dołączone do kontekstu, EF wygeneruje DELETE oświadczenie dla każdego załączonego dziecka, to dla rodzica (bo Remove zrobił oznaczyć je wszystkie jako Deleted)
    • Jeśli dzieci nie są dołączone do kontekstu EF wyśle ​​tylko instrukcję DELETE dla bazy danych do bazy danych, a ponieważ usuwanie kaskadowe jest włączone, baza danych usunie również dzieci.
  • Jeśli ustawisz stan podmiotu głównego do Deleted można ewentualnie uzyskać wyjątek:
    • Jeśli dzieci są dołączone do kontekstu ich stan nie zostanie ustawiony na Deleted i EF będzie narzekają, że próbujesz usunąć główną (podmiot root) w wymaganym związku bez usuwania osób na utrzymaniu (dzieci) lub przynajmniej bez ustawiania swoich kluczy obcych do innego podmiotu root, który nie jest w stanie Deleted. To wyjątek miałeś: account jest korzeniem i user1 jest zależny od account i nazywając context.Entry(account).State = EntityState.Deleted; będzie również dołączyć user1 w stanie Unchanged do kontekstu (lub zmienić wykrywanie w SaveChanges to zrobi, nie jestem pewien, że stykają się). user1 jest częścią kolekcji account.Users, ponieważ korekta relacji dodała ją do kolekcji w pierwszym kontekście, mimo że nie została ona dodana bezpośrednio w kodzie.
    • Jeśli żadne dzieci nie są dołączone do ustawienia kontekstu, stan root do Deleted wyśle ​​do bazy danych instrukcję DELETE i ponownie usuwanie kaskadowe w bazie danych usunie również potomka. Działa to bez wyjątku. Twój kod będzie działał na przykład, jeśli ustawisz account.Users = null przed ustawieniem stanu na Deleted w drugim kontekście lub przed wprowadzeniem drugiego kontekstu.

Moim zdaniem przy użyciu Remove ...

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

... jest wyraźnie preferowana droga, ponieważ zachowanie Remove jest znacznie bardziej jak można się spodziewać na wymaganym relacji z kaskadowych usuń (co ma miejsce w twoim modelu). Zależność zachowania ręcznej zmiany stanu na stanach innych podmiotów sprawia, że ​​korzystanie z niej jest trudniejsze. Uznałbym to za zaawansowane użycie tylko w przypadkach specjalnych.

Różnica nie jest powszechnie znana lub udokumentowana. Widziałem bardzo mało postów na ten temat. Jedyny, który mogłem znaleźć teraz, to this one by Zeeshan Hirani.

+1

Bardzo pouczające, @Slauma! Przed wysłaniem pytania wiele szukałem, ale nie znalazłem tego, o którym wspomniałeś. Dziękuję –

+0

Wygląda na to, że wiele osób ma tak wiele problemów podczas próbowania wykonywania operacji rodzic-dziecko w EF, takich jak wstawianie, aktualizowanie i usuwanie, za każdym razem, gdy czytam więcej o EF, tym bardziej jestem rozczarowany. –

1

Próbowałem trochę innego podejścia i, co dziwne, zadziałało. Gdybym zastąpić ten kod:

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

Przez ten jeden:

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

Działa bez dalszych problemów. Nie jestem pewien, czy to błąd, czy coś mi brakuje. Naprawdę doceniłbym trochę światła w tej sprawie, ponieważ byłem pewien, że pierwszy sposób (EntityState.Deleted) był polecany.

Powiązane problemy