如何在EF代码优先数据库中删除一对多的子记录?


70

好吧,我有一对多的相关模型:

public class Parent
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<Child> Children { get; set; }
}

public class Child
{
    public int Id { get; set; }
    public string ChildName { get; set; }
}

我要做的是清除Parent.Children并从数据库中删除相关的子实体。我已经尝试过:

数据库上下文类:

modelBuilder.Entity<Parent>()
            .HasMany(p => p.Children)
            .WithOptional()
            .WillCascadeOnDelete(true);

这可以正常工作,但是Parent_Id = null当我这样做时,数据库中仍然有带有字段的冗余记录

parent.Children.Clear();
repository.InsertOrUpdate(parent);

在我的存储库类中。当我这样做时,也是相同的行为:

modelBuilder.Entity<Parent>()
            .HasMany(pr => pr.Children)
            .WithOptional(ri => ri.Parent)
            .WillCascadeOnDelete(true);

ParentChild课堂上有额外的财产

public class Child
{
    ...
    public Parent Parent { get; set; }
    ...
}

或当我做

modelBuilder.Entity<Child>()
            .HasOptional(p => p.Parent)
            .WithMany(p => p.Children)
            .HasForeignKey(p => p.Parent_Id)
            .WillCascadeOnDelete(true);

Child类中具有其他Parent_Id属性

public class Child
{
     ...
     public int Parent_Id { get; set; }
     ...
}

那么,如何正确配置级联删除?还是应该删除这些子实体?我认为这是一项随便的任务,但我只是缺少一些东西。


您还能发布更多代码吗?也许您的全部内容OnModelCreating()?当我复制粘贴您的实体并进行第一次映射尝试(并将其设置Id为两个实体的键属性)时,删除对我来说是正确的级联。
杰里米·托德

Answers:


69

级联删除在这里无效,因为您不删除parent而是调用InsertOrUpdate。正确的过程是一个一个地删除子级,例如:

using (var context = new MyContext())
{
    var parent = context.Parents.Include(p => p.Children)
        .SingleOrDefault(p => p.Id == parentId);

    foreach (var child in parent.Children.ToList())
        context.Children.Remove(child);

    context.SaveChanges();
}

当然,使用层叠删除时,您不需要单独删除子级吗?
Kirsten Greed

7
@kirsteng:级联删除表示如果删除父级,删除子级。但是在这个问题上,没有父母被删除。因此,级联删除不适用于此方案。
Slauma 2013年

22
不用说所有的“子”对象,只需说context.Children.RemoveRange(parent.Children.ToArray())一遍,DbContext不必在每次调用Remove时都做很多工作。对于删除来说,这可能不是一个大的性能问题,但是当我测试context.Children.Add(child)在for循环中使用一次添加100k条记录时一次添加一项时,我注意到了巨大的差异。
C. Tewalt 2015年

1
在这种情况下,至少在使用EF Core时,级联删除才有效:)
Konrad,

1
@Konrad感谢您的提示。就我而言,我不得不更改数据库架构以具有ON DELETE CASCADE并再次生成数据库模型以包含DeleteBehavior.Cascade在其中。
加里·特尔基亚

108

在EF6中,更快的操作方法是...

 context.Children.RemoveRange(parent.Children)

这是一种更快的编码方式,但就性能而言,真的更快吗?如果要删除带有的父项下的所有子项parent.Id == 10,并且我的Child班级有100个字段并且尚未加载到内存中,则使用效率似乎较低,RemoveRange因为parent.Children默认情况下该程序将必须加载所有100个字段。
VCD

2
@VCD比调用每个孩子的context.Children.Remove(child)更快,但不如运行sql“ Delete
From

如果您唯一拥有的信息是孩子ID,您将如何寻找父母?
guyfromfargo

@guyfromfargo在EF类似var child = context.Children.Single(f=>f.Id==childId); var parent = context.Parents.Single(f=>f.Id==child.ParentId);
山姆SIPPE


2

尝试更改为

 public virtual ICollection<Child> Children { get; set; }

因为需要虚拟才能获得延迟加载。如这里解释

我认为您的parent.Children.clear无法正常工作,因为未加载孩子


1
就我而言(ASP.NET core 2 / EF Core),我已经测试过了,但这不是解决方案
hamid reza

1

如果您的对象是自引用对象,则可以使用以下方法删除多对多和一对多子代。只是记得之后要调用db.SaveChanges():)

[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
    Object obj = this.db.Objects.Find(id);
    this.DeleteObjectAndChildren(obj);
    this.db.Objects.Remove(obj);
    this.db.SaveChanges();
    return this.Json(new { success = true });
}

/// <summary>
/// This deletes an object and all children, but does not commit changes to the db.
///  - MH @ 2016/08/15 14:42
/// </summary>
/// <param name="parent">
/// The object.
/// </param>
private void DeleteObjectAndChildren(Object parent)
{
    // Deletes One-to-Many Children
    if (parent.Things != null && parent.Things.Count > 0)
    {
        this.db.Things.RemoveRange(parent.Things);
    }

    // Deletes Self Referenced Children
    if (parent.Children != null && parent.Children.Count > 0)
    {
        foreach (var child in parent.Children)
        {
            this.DeleteObjectAndChildren(child);
        }

        this.db.Objects.RemoveRange(parent.Children);
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.