2012-11-20 15 views
7

Używam DDD. Mam produkt klasy, który jest zbiorczym katalogiem głównym.Usuń element z kolekcji przy użyciu Entity Framework

public class Product : IAggregateRoot 
{ 
    public virtual ICollection<Comment> Comments { get; set; } 

    public void AddComment(Comment comment) 
    { 
     Comments.Add(comment); 
    } 

    public void DeleteComment(Comment comment) 
    { 
     Comments.Remove(comment); 
    } 
} 

Warstwa, która zawiera modele, wcale nie wie o EF. Problem polega na tym, że gdy zadzwonię pod numer DeleteComment(comment), EF zgłasza wyjątek

Relacja z zestawu "Product_Comments" AssociationSet znajduje się w stanie "Deleted". Biorąc pod uwagę ograniczenia wielokrotności, odpowiedni "Product_Comments_Target" musi również znajdować się w stanie "Usunięte".

Nawet jeśli element zostanie usunięty z kolekcji, EF go nie usunie. Co powinienem zrobić, aby to naprawić, nie łamiąc DDD? (Mam na myśli podejmowania repozytorium również w komentarzach, ale nie jest w porządku)

przykładowy kod:

Bo próbuję użyć DDD The Product jest agregatem korzeń, i ma repozytorium IProductRepository. Komentarz nie może istnieć bez produktu, w związku z tym jest dzieckiem o nazwie Product Aggregate, a Product odpowiada za tworzenie i usuwanie komentarzy. Comment nie ma repozytorium.

public class ProductService 
{ 
    public void AddComment(Guid productId, string comment) 
    { 
     Product product = _productsRepository.First(p => p.Id == productId); 
     product.AddComment(new Comment(comment)); 
    } 

    public void RemoveComment(Guid productId, Guid commentId) 
    { 
     Product product = _productsRepository.First(p => p.Id == productId); 
     Comment comment = product.Comments.First(p => p.Id == commentId); 
     product.DeleteComment(comment); 


     // Here i get the error. I am deleting the comment from Product Comments Collection, 
     // but the comment does not have the 'Deleted' state for Entity Framework to delete it 

     // However, i can't change the state of the Comment object to 'Deleted' because 
     // the Domain Layer does not have any references to Entity Framework (and it shouldn't) 

     _uow.Commit(); // UnitOfWork commit method 

    } 
} 
+1

Wygląda na to, że nie wywołujesz SaveChanges EF – Nagg

+0

Domyślam się, że istnieje tabela o nazwie Target. Ta tabela ma odwołanie FK do tabeli komentarzy. Podczas próby usunięcia wiersza w tabeli komentarzy powiązane wiersze w obiekcie docelowym muszą zostać usunięte jako pierwsze. –

+0

@Nagg Otrzymuję ten błąd, gdy wywołuję SubmitChanges() – Catalin

Odpowiedz

1

Usuwanie Komentarz produkt przy użyciu swoje podejście usuwa tylko skojarzenie pomiędzy produktem i komentować. Aby komentarz nadal istniał.

Należy wskazać obiektowi obiektowi, że komentarz jest również usuwany przy użyciu metody DeleteObject().

tak jak ja to jest, że używam metody Update mojego repozytorium (wie, Entity Framework) w celu sprawdzenia usuniętych stowarzyszeń i również usunąć zbędne elementy. Możesz to zrobić za pomocą ObjectStateManager z ObjectContext.

public void UpdateProduct(Product product) { 
    var modifiedStateEntries = Context.ObjectStateManager.GetObjectStateEntries(EntityState.Modified); 
    foreach (var entry in modifiedStateEntries) { 
     var comment = entry.Entity as Comment; 
     if (comment != null && comment.Product == null) { 
     Context.DeleteObject(comment); 
     } 
    } 
} 

Próbka:

public void RemoveComment(Guid productId, Guid commentId) { 
    Product product = _productsRepository.First(p => p.Id == productId); 
    Comment comment = product.Comments.First(p => p.Id == commentId); 
    product.DeleteComment(comment); 

    _productsRepository.Update(product); 

    _uow.Commit(); 
} 
+0

Miał podobny problem - DeleteObject() działa jak urok. –

+0

Nie, nie trzeba dotykać narzędzia Context.ObjectStateManager i zdecydowanie zalecamy, aby NIGDY nie odtwarzać ze stanem kontekstu, chyba że masz ku temu prawdziwy powód. Aby rozwiązać ten problem, musisz ustawić klucz kompozytowy na dziecku. W ten sposób, po usunięciu dziecka z jego rodzica, EF usunie relację ORAZ usunie również obiekt podrzędny. Popatrz na moją odpowiedź poniżej dla przykładu kodu. – Mosh

11

Widziałem wiele osób zgłaszających ten problem. Jest to dość proste do naprawienia, ale wydaje mi się, że nie ma wystarczającej dokumentacji na temat tego, jak EF powinien zachowywać się w tej sytuacji.

trick: Podczas konfigurowania relacji między rodzicem a dzieckiem, musisz utworzyć klucz „kompozytowe” na dziecko. W ten sposób, gdy powiesz rodzicowi, aby usunął 1 lub wszystkie jego elementy podrzędne, powiązane rekordy zostaną usunięte z bazy danych.

Aby skonfigurować klucz kompozytowych przy użyciu Fluent API:

modelBuilder.Entity<Child>.HasKey(t => new { t.ParentId, t.ChildId }); 

Następnie, aby usunąć związanych dzieci:

var parent = _context.Parents.SingleOrDefault(p => p.ParentId == parentId); 

var childToRemove = parent.Children.First(); // Change the logic 
parent.Children.Remove(childToRemove); 

// or, you can delete all children 
// parent.Children.Clear(); 

_context.SaveChanges(); 

Gotowe!

2

Widziałem 3 podejścia do obejść ten niedobór EF:

  1. skonfigurować klucz kompozytowych (jak za odpowiedź Mosh'S)
  2. podnieść zdarzenie domeny i poinstruować EF wykonywać delecji dziecko w jej obsługi (zgodnie this odpowiedzi)
  3. Zastąp DbContext „s SaveChanges() i obsługiwać usunięcie tam (jak na odpowiedź euforii za)

Najbardziej podoba mi się opcja 3, ponieważ nie wymaga modyfikacji struktury bazy danych (1) lub modelu domeny (2), ale umieszcza obejście w komponencie (EF), w którym w pierwszej kolejności wystąpił niedobór.

Więc to jest zaktualizowaną rozwiązanie zaczerpnięte z euforii za odpowiedź/blogu:

public class MyDbContext : DbContext 
{ 
    //... typical DbContext stuff 

    public DbSet<Product> ProductSet { get; set; } 
    public DbSet<Comment> CommentSet { get; set; } 

    //... typical DbContext stuff 


    public override int SaveChanges() 
    { 
     MonitorForAnyOrphanedCommentsAndDeleteThemIfRequired(); 
     return base.SaveChanges(); 
    } 

    public override Task<int> SaveChangesAsync() 
    { 
     MonitorForAnyOrphanedCommentsAndDeleteThemIfRequired(); 
     return base.SaveChangesAsync(); 
    } 

    public override Task<int> SaveChangesAsync(CancellationToken cancellationToken) 
    { 
     MonitorForAnyOrphanedCommentsAndDeleteThemIfRequired(); 
     return base.SaveChangesAsync(cancellationToken); 
    } 

    private void MonitorForAnyOrphanedCommentsAndDeleteThemIfRequired() 
    { 
     var orphans = ChangeTracker.Entries().Where(e => 
      e.Entity is Comment 
      && (e.State == EntityState.Modified || e.State == EntityState.Added) 
      && (e.Entity as Comment).ParentProduct == null); 

     foreach (var item in orphans) 
      CommentSet.Remove(item.Entity as Comment); 
    } 
} 

Uwaga: ten zakłada, że ​​ParentProduct jest własnością nawigacja na Comment powrotem do jej posiadania Product.

Powiązane problemy