使用Entity Framework从Collection中删除项目

时间:2012-11-20 12:45:35

标签: c# entity-framework ef-code-first domain-driven-design

我正在使用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

    }
}

5 个答案:

答案 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)

以下是一对相关的解决方案:

Delete Dependent Entities When Removed From EF Collection

答案 2 :(得分:5)

我已经看到了解决EF中这种缺陷的3种方法:

  1. 配置复合键(根据Mosh的回答)
  2. 提升域事件并指示EF在其处理程序中执行子删除(根据this回答)
  3. 覆盖DbContext&#39; SaveChanges()并在那里处理删除(根据Euphoric的答案)
  4. 我最喜欢选项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);
        }
    }
    

    注意:这假设ParentProductComment上的导航属性,返回其拥有的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