【问题标题】:Remove an item from Collection using Entity Framework使用实体框架从集合中删除一个项目
【发布时间】:2012-11-20 12:45:34
【问题描述】:

我正在使用 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 的 SaveChanges
  • 我猜有一个表叫Target。此表具有对 Comments 表的 FK 引用。当您尝试删除 Comment 表中的行时,需要先删除 Target 中的关联行。
  • @Nagg 我在调用 SubmitChanges() 时收到此错误
  • @ErcFan 不,我没有表,我认为这是实体框架创建的默认关系名称。 (我使用的是代码优先)
  • @RaraituL 你能解释更多代码吗?

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


【解决方案1】:

我看到很多人报告了这个问题。它实际上很容易修复,但让我觉得没有足够的文档说明 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();

完成!

【讨论】:

    【解决方案2】:

    这是一对相关的解决方案:

    Delete Dependent Entities When Removed From EF Collection

    【讨论】:

      【解决方案3】:

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

      1. 配置复合键(根据 Mosh 的回答)
      2. 引发域事件并指示 EF 在其处理程序中执行子删除(根据 this 答案)
      3. 覆盖 DbContextSaveChanges() 并在那里处理删除(根据 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);
          }
      }
      

      注意:这假定ParentProductComment 上的导航属性,返回到其拥有的Product

      【讨论】:

        【解决方案4】:

        使用您的方法从产品中删除评论只会删除产品和评论之间的关联。这样评论仍然存在。

        你需要做的是告诉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();
        }
        

        【讨论】:

        • 不,您不需要触摸 Context.ObjectStateManager,我强烈建议您不要玩弄上下文的状态,除非您有真正的理由这样做。要解决此问题,您需要在子项上设置一个复合键。这样,当您从其父级中删除子级时,EF 将删除关系并删除子实体。在下面查看我的答案以获取代码示例。
        【解决方案5】:

        我通过为我的模型创建父属性并检查 SaveChanges 函数中的属性解决了同样的问题。我为此写了一篇博客:http://wimpool.nl/blog/DotNet/extending-entity-framework-4-with-parentvalidator

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2016-05-25
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多