我正在使用DDD。我有一个 Product 类,它是一个聚合根。
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”关联集中的关系处于“已删除”状态。考虑到多重性约束,相应的“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
}
}
我看到很多人报告这个问题。实际上修复起来非常简单,但让我觉得没有足够的文档来说明 EF 在这种情况下的行为方式。
技巧:在设置父级和子级之间的关系时,您必须在子级上创建一个“复合”键。这样,当您告诉父级删除 1 个或所有子级时,相关记录实际上将从数据库中删除。
使用 Fluent 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();
完成!
这是一对相关的解决方案:
我已经看到了 3 种方法来解决 EF 中的这一缺陷:
DbContext
的SaveChanges()
并在那里处理删除(根据Euphoric的答案)我最喜欢选项 3,因为它不需要修改数据库结构 (1) 或域模型 (2),而是将解决方法放在首先存在缺陷的组件 (EF) 中。
所以这是取自 Euphoric 的答案/博客文章的更新解决方案:
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);
}
}
注意:这假设
ParentProduct
是 Comment
上回到其所属 Product
的导航属性。
使用您的方法从产品中删除评论只会删除产品和评论之间的关联。这样评论仍然存在。
你需要做的是使用方法
DeleteObject()
告诉ObjectContext Comment也被删除了。
我这样做的方式是使用我的存储库的 Update 方法(了解实体框架)来检查已删除的关联并删除过时的实体。您可以使用 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();
}
我通过为模型创建父属性并检查 SaveChanges 函数中的属性解决了同样的问题。我写了一篇关于此的博客:http://wimpool.nl/blog/DotNet/extending-entity-framework-4-with-parentvalidator