我正在使用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
}
}
答案 0 :(得分:12)
我见过很多人报道此问题。它实际上很容易修复,但让我觉得没有足够的文档说明EF在这种情况下的行为方式。
技巧:在设置父母和孩子之间的关系时,你必须创建一个&#34;复合&#34;关键的孩子。这样,当您告诉Parent删除其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();
完成!
答案 1 :(得分:6)
答案 2 :(得分:5)
我已经看到了解决EF中这种缺陷的3种方法:
DbContext
&#39; 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
。
答案 3 :(得分:1)
使用您的方法从产品中删除评论只会删除产品和评论之间的关联。所以评论仍然存在。
您需要做的是告诉ObjectContext还使用方法DeleteObject()
删除了注释。
我这样做的方式是我使用我的存储库的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();
}
答案 4 :(得分:0)
我通过为模型创建父属性并检查SaveChanges函数中的属性解决了同样的问题。我写过一篇关于此的博客:http://wimpool.nl/blog/DotNet/extending-entity-framework-4-with-parentvalidator