2012-05-31 9 views
13

W przypadku aplikacji przy użyciu kodu Pierwszy EF 5 beta mam:Czy EF może automatycznie usuwać dane osierocone, jeśli rodzic nie został usunięty?

public class ParentObject 
{ 
    public int Id {get; set;} 
    public virtual List<ChildObject> ChildObjects {get; set;} 
    //Other members 
} 

i

public class ChildObject 
{ 
    public int Id {get; set;} 
    public int ParentObjectId {get; set;} 
    //Other members 
} 

odpowiednie operacje CRUD wykonywane są przez repozytoria, w razie potrzeby.

W

OnModelCreating(DbModelBuilder modelBuilder) 

mam je ustawić:

modelBuilder.Entity<ParentObject>().HasMany(p => p.ChildObjects) 
      .WithOptional() 
      .HasForeignKey(c => c.ParentObjectId) 
      .WillCascadeOnDelete(); 

Więc jeśli ParentObject zostanie usunięty, jego ChildObjects są zbyt.

Jednakże, jeśli biegnę:

parentObject.ChildObjects.Clear(); 
_parentObjectRepository.SaveChanges(); //this repository uses the context 

otrzymuję wyjątek:

Operacja nie powiodła się: Związek nie mógł być zmieniony, ponieważ jeden lub więcej obcych kluczowych właściwości nie jest -nullable. Gdy wprowadzana jest zmiana relacji, 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 obiekt niepowiązany musi zostać usunięty.

Ma to sens, ponieważ definicja jednostek obejmuje złamanie klucza obcego.

Czy mogę skonfigurować jednostkę, aby "czyściła się", gdy zostanie osierocona, lub musi ręcznie usunąć te ChildObject s z kontekstu (w tym przypadku przy użyciu ChildObjectRepository).

+0

szczęście, zespół EF [wie o tym] (http://blog.oneunicorn.com/2012/06/02/deleting-orphans-with-entity-framework/) i będzie prawdopodobnie pochodzić z wbudowane rozwiązanie, które nie wymaga modyfikacji elementów wewnętrznych – PinnyM

Odpowiedz

25

To jest faktycznie obsługiwane, ale tylko w przypadku korzystania Identifying relation. Działa również z kodem. Trzeba tylko określić złożony klucz do ChildObject zawierającej zarówno Id i ParentObjectId:

modelBuilder.Entity<ChildObject>() 
      .HasKey(c => new {c.Id, c.ParentObjectId}); 

Ponieważ definiowanie takiego klucza usunie domyślną konwencję auto zwiększane Id trzeba go przedefiniować ręcznie:

modelBuilder.Entity<ChildObject>() 
      .Property(c => c.Id) 
      .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); 

Teraz nazywając do parentObject.ChildObjects.Clear() usuwa zależne obiekty.

Przy okazji.Twój mapowanie relacja powinna wykorzystać WithRequired śledzić swoje prawdziwe zajęcia, bo jeśli FK nie jest pustych, to nie jest opcja:

modelBuilder.Entity<ParentObject>().HasMany(p => p.ChildObjects) 
      .WithRequired() 
      .HasForeignKey(c => c.ParentObjectId) 
      .WillCascadeOnDelete(); 
+0

Świetna odpowiedź. Wygląda na to, że muszę się dużo nauczyć o EF. – StuperUser

+1

@ Ladislav Mmka co z przypadkiem jednokierunkowym? Kiedy ChildObject nie wie o rodzicu? – Davita

+0

@Ladislav Używam EF6 i próbowałem zrobić tak, jak zrobiłeś w przykładach. Jednak wywołanie metody Collection.Clear() i AddOrUpdate (rodzic) nie działa. Czy jest coś, co muszę zrobić w kontekście? – Jamez

-1

To nie jest coś, co jest teraz automatycznie wspierane przez EF. Możesz to zrobić, zastępując SaveChanges w kontekście i ręcznie usuwając obiekty podrzędne, które nie mają już elementu nadrzędnego. Kod byłoby coś takiego:

public override int SaveChanges() 
{ 
    foreach (var bar in Bars.Local.ToList()) 
    { 
     if (bar.Foo == null) 
     { 
      Bars.Remove(bar); 
     } 
    } 

    return base.SaveChanges(); 
} 
+3

Dobry pomysł, z wyjątkiem tego, który powinien dać ci straszną wydajność –

+3

Czy ten kod nie zostanie wykonany dla każdego SaveChanges()? – Elisabeth

4

Aby rozwiązać ten problem bez konfigurowania złożonego klucza, można zastąpić SaveChanges twoich DbContext, ale następnie użyj ChangeTracker, aby uniknąć dostępu do bazy danych, aby znaleźć obiekty osierocone.

Pierwszy dodać obiekt nawigacyjny z ChildObject (można zachować int ParentObjectId własność, jeśli chcesz, to działa w obie strony):

public class ParentObject 
{ 
    public int Id { get; set; } 
    public virtual List<ChildObject> ChildObjects { get; set; } 
} 

public class ChildObject 
{ 
    public int Id { get; set; } 
    public virtual ParentObject ParentObject { get; set; } 
} 

Następnie poszukaj obiektów osieroconych za pomocą ChangeTracker:

public class MyContext : DbContext 
{ 
    //... 
    public override int SaveChanges() 
    { 
     HandleOrphans(); 
     return base.SaveChanges(); 
    } 

    private void HandleOrphans() 
    { 
     var orphanedEntities = 
      ChangeTracker.Entries() 
      .Where(x => x.Entity.GetType().BaseType == typeof(ChildObject)) 
      .Select(x => ((ChildObject)x.Entity)) 
      .Where(x => x.ParentObject == null) 
      .ToList(); 

     Set<ChildObject>().RemoveRange(orphanedEntities); 
    } 
} 

Twoja konfiguracja staje się:

modelBuilder.Entity<ParentObject>().HasMany(p => p.ChildObjects) 
      .WithRequired(c => c.ParentObject) 
      .WillCascadeOnDelete(); 

Zrobiłem prosty test prędkości iteracyjny 10.000 razy. Przy włączonej HandleOrphans() zajęło 1: 01.443 min, z tym wyłączeniem było 0: 59,326 min (obie są średnią z trzech przebiegów). Testuj kod poniżej.

using (var context = new MyContext()) 
{ 
    var parentObject = context.ParentObject.Find(1); 
    parentObject.ChildObjects.Add(new ChildObject()); 
    context.SaveChanges(); 
} 

using (var context = new MyContext()) 
{ 
    var parentObject = context.ParentObject.Find(1); 
    parentObject.ChildObjects.Clear(); 
    context.SaveChanges(); 
} 
Powiązane problemy