【问题标题】:Getting Entity Framework to cascade-delete when re-attaching modified entity重新附加修改后的实体时让实体框架级联删除
【发布时间】:2012-11-08 16:06:07
【问题描述】:

我正在使用 MVC4 和 EF 代码优先方法开发一个网站。

我在删除具有一对多关系的实体中的子项时遇到了一些问题。

编辑以清除:在我的编辑视图中,我添加/删除/更新父级子集集合中的现有子级,添加/删除是使用 javascript 完成的。当我在控制器方法的 post 请求中收到更新的父级时,我想同步/更新数据库中的父级和子级实体。 父对象在视图中更新时处于分离状态。因此,当我再次附加父级时,我希望它完成在分离状态期间完成的所有更新。

设置实体关系,以便在从父集合中删除子实体时,子实体也会从子表中删除(级联删除之类的?),这在处于附加状态时有效。

但是,当附加父级并保存更改时,只有添加/更新的子级会在数据库中添加/修改。但是从父集合中删除的子项不会在数据库中删除(我希望它们是)。

如何解决?

实体是:

class Parent
{
    public virtual ICollection<Child> Children { get; set; }
}
class Child
{
    public string Text { get; set; }
}

这可行,并且将从数据库中删除子项

void RemoveChildFromCollection()
{
    // get the first parent and remove the first child in collection
    var context = new DatabaseContext();
    var parent = context.Parents.First();
    parent.Children.Remove(parent.Children.First());
    context.SaveChanges();
}

ControllerMethod: 这不能像上面那样工作,被移除的孩子不会从孩子的表中移除

public ActionResult Edit(Parent parent)
{
    var context = new DatabaseContext();
    context.Entry(parent).State = System.Data.EntityState.Modified;
    context.SaveChanges();
    return View();
}

模型构建器设置为在从父集合中删除子表时从子表中删除子实体

// Use Identifying relation. Define complex key for ChildObject containing both Id and 

ParentObjectId
modelBuilder.Entity<Child>()
    .HasKey(c => new {c.ChildID, c.ParentID});

// Because defining such key will remove default convention for auto incremented Id you must redefine it manually

modelBuilder.Entity<Child>()
    .Property(c => c.ChildID)
    .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

// Set cascade delete

modelBuilder.Entity<Parent>()
    .HasMany(p => p.Children)
    .WithRequired()
    .HasForeignKey(c => c.ParentID)
    .WillCascadeOnDelete();

【问题讨论】:

  • 我在下面的答案中添加了一个代码 sn-p 以使其更清晰。

标签: asp.net-mvc entity-framework ef-code-first


【解决方案1】:

级联删除仅删除子实体,当父实体也被删除时,正如您所指出的,而不是在您切断关系时。

您可以在 Context 中覆盖 SaveChanges() 以清理孤立的 Child 实体,如下所示:

public override int SaveChanges()
{
    Children
        .Local
        .Where(c => c.Parent == null)
        .ToList()
        .ForEach(child => Children.Remove(child));

    return base.SaveChanges();
}

这个blog post 有更多关于处理孤立实体的信息。

【讨论】:

  • 嗯,如果在分离状态下移除孩子,这真的有效吗?视图中没有可用的 EF 上下文可以将孩子的 Parent 设置为 null。在附加状态下,他没有孤儿记录的问题,因为父子关系是一种识别关系,即它会自动删除孤儿。
  • 是否处于分离状态?他的问题是:“但是,当从视图中的父集合中删除子项时,EF 不会删除该子项……”这意味着子项已附加,然后在视图中分离。我想真正的问题是,正如您的回答所说 - 他究竟是如何带走孩子的。如果在他的控制器操作中删除孩子,而不是简单地删除,这实际上可能更容易。
  • 我实际上是 猜测(我可能错了)他在 GET 请求中加载原始图表,将其传递给视图,一些 - 也许 - Javascript 删除了子,视图被回发,并且由 MVC 模型绑定器 (Edit(Parent parent)) 创建了一个分离的对象图,该图现在少了一个子。但是哪一个?发布操作不知道要删除什么。如果他在其中一个孩子中发布完整的原始集合,可能带有Deleted 标志,那么是的,他可以在控制器中删除那个。
  • @Slauma,你来对了!我使用一些 javasctipt 更新了视图中的子集合,但是当我在发布请求中收到更新的父级时,我不知道更新了什么。我会努力澄清的。
【解决方案2】:

当您在视图中进行修改时,您的实体(父实体和子实体)处于分离状态。因此,EF 无法跟踪这些更改。当您将对象图附加到上下文时 - 通过将父级的状态设置为 Modified - EF 将此附加的对象图作为当前状态,并且不知道在分离阶段中发生的子删除查看。

要解决此问题,您必须从数据库加载当前对象图(包括子对象的父对象),将其与视图中的对象图进行比较,并将更改合并到加载的图中。然后保存更改。可能有几种可能的变化:

  • 父级的标量属性可能已更改
  • 可以更改子级的标量属性
  • 新的孩子可能已添加到 Children 集合中
  • 一个孩子可能已从 Children 集合中删除

您当前的代码 - 将父级的状态设置为 Modified - 只会正确处理第一种情况,而不能正确处理其他三种情况。

要处理所有四种情况,您需要遵循上述过程。 here 显示了如何执行此操作的示例(请参阅该答案中的 编辑 部分)。

编辑帖子操作中的代码将如下所示:

public ActionResult Edit(Parent parent)
{
    using (var context = new DatabaseContext())
    {
        var parentInDb = context.Parents
            .Include(p => p.Children)
            .Single(p => p.ParentId == parent.ParentId);

        context.Entry(parentInDb).CurrentValues.SetValues(parent);

        foreach (var childInDb in parentInDb.Children.ToList())
            if (!parent.Children.Any(c => 
                    c.ChildId == childInDb.ChildId &&
                    c.ParentId == childInDb.ParentId)) // or == parent.ParentId
                context.Children.Remove(childInDb);
                // here
                // parentInDb.Children.Remove(childInDb);
                // should work too because you have an identifying relationship

        foreach (var child in parent.Children)
        {
            var childInDb = parentInDb.Children.SingleOrDefault(c =>
                c.ChildId == child.ChildId &&
                c.ParentId == child.ParentId); // or == parent.ParentId
            if (childInDb != null)
                context.Entry(childInDb).CurrentValues.SetValues(child);
            else
                parentInDb.Children.Add(child);
        }
        context.SaveChanges();

        return View();
    }
}

【讨论】:

    猜你喜欢
    • 2015-10-01
    • 1970-01-01
    • 2014-12-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多