0

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

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

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

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

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

这个怎么解决??

实体是:

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();
4

2 回答 2

2

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

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

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

    return base.SaveChanges();
}

博客文章包含有关处理孤立实体的更多信息。

于 2012-11-08T16:35:09.530 回答
1

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

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

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

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

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

您的 Edit post 操作中的代码将如下所示:

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();
    }
}
于 2012-11-08T16:36:10.073 回答