在重新附加修改的实体时使实体框架级联删除

时间:2012-11-08 16:06:07

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

我正在使用MVC4开发一个使用EF代码第一种方法的网站。

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

编辑以清除:在我的编辑视图中,我在父级的子集合中添加/删除/更新现有的childen,使用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();

2 个答案:

答案 0 :(得分:2)

当您删除父实体时,级联删除仅删除子实体,如您所述,而不是在您断开关系时。

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

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

    return base.SaveChanges();
}

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

答案 1 :(得分:1)

当您在视图中进行修改时,您的实体(父实体和子实体)处于分离状态。因此,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();
    }
}