2012-11-20 65 views
7

我正在使用DDD。我有一個類聚合的根產品。使用實體框架從集合中刪除項目

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

容納模型的圖層根本不知道EF。問題是當我撥打DeleteComment(comment)時,EF引發異常

來自'Product_Comments'AssociationSet的關係處於'已刪除'狀態。考慮到多重性約束,相應的「Product_Comments_Target」也必須處於「已刪除」狀態。

即使該元素從集合中刪除,EF也不會將其刪除。我應該怎麼做才能解決這個問題而不會打破DDD? (我想做出註解,以及一個存儲庫,而不是右)

代碼示例:

因爲我嘗試使用DDD,該Product是一個聚合根,它有一個版本庫IProductRepository。如果沒有產品,則評論不能存在,因此是Product總計的子女,而Product負責創建和刪除評論。 Comment沒有存儲庫。

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

似乎你不打電話EF的SaveChanges – Nagg

+0

我想有一個名爲目標的表。此表格具有FK對評論表的引用。當您嘗試刪除評論表中的行時,需要首先刪除目標中的關聯行。 –

+0

@Nagg當我打電話SubmitChanges() – Catalin

回答

1

使用您的方法從產品中刪除評論只會刪除產品和評論之間的關聯。所以評論依然存在。

您需要做的是告訴ObjectContext,該評論也使用方法DeleteObject()刪除。

我這樣做的方式是,我使用我的存儲庫的更新方法(知道實體框架)來檢查已刪除的關聯並刪除過時的實體。你可以通過使用ObjectContext的ObjectStateManager來做到這一點。

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

樣品:

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

有一個類似的問題 - DeleteObject()就像一個魅力。 –

+0

不,您不需要觸摸Context.ObjectStateManager,我強烈建議您不要隨意使用上下文的狀態,除非您有真正的理由這麼做。爲了解決這個問題,你需要在孩子身上設置一個複合鍵。通過這種方式,當您將子項從其父項中移除時,EF將刪除關係並刪除子項實體。查看下面的代碼示例的答案。 – Mosh

11

我見過很多人報告這個問題。這實際上很容易解決,但讓我覺得沒有足夠的文件說明EF如何在這種情況下表現出來。

絕招:設置父母與子女之間的關係時,您必須在子女上創建一個「合成」鍵。這樣,當您告訴父節點刪除1或其所有子節點時,實際上將從數據庫中刪除相關記錄。

要使用流利的API配置組合鍵:

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

然後,刪除有關兒童:

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

完成!

2

我已經看到了3種方法來解決此不足在EF:

  1. 配置組合鍵(按MOSH的答案)
  2. 提升域事件,並指示EF在執行兒童刪除其處理(按照this答案)
  3. 覆蓋DbContextSaveChanges()和處理刪除有(按欣快的答案)

我最喜歡選項3,因爲它不需要修改您的數據庫結構(1)或您的域模型(2),但會將解決方法放在首先存在缺陷的組件(EF)中。

所以這是欣快的答案/博客文章採取了更新的解決方案:

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

注:這假定ParentProductComment後面的導航屬性到它所屬的Product