How to remove child one to many related records in EF code first database?

C#Entity FrameworkCode First

C# Problem Overview


Well, I have one-to-many related model:

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; }
}

What I want to do is clear Parent.Children and remove related child entities from database. I've already tried:

Database context class:

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

this works fine, but I still have redundant records in database with Parent_Id = null fields when I do

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

in my repository class. Also the same behavior is when I do:

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

with additional Parent property in Child class

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

or when I do

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

with additional Parent_Id property in Child class

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

So, how can I configure cascade deleting correctly? Or how should I supposed to remove those child entities? I assume this is casual task but I'm just missing something.

C# Solutions


Solution 1 - C#

In EF6 a faster way to do the operation is...

 context.Children.RemoveRange(parent.Children)

Solution 2 - C#

Cascading delete has no effect here because you don't delete the parent but just call InsertOrUpdate. The correct procedure is to delete the children one-by-one, like so for example:

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

Solution 3 - C#

This is called "deleting orphans".

https://stackoverflow.com/questions/10835439/can-ef-automatically-delete-data-that-is-orphaned-where-the-parent-is-not-delet

I don't know how it works in EF6 but in EF Core it works fine https://docs.microsoft.com/en-us/ef/core/saving/cascade-delete so you don't necessarily need to delete the parent for cascades to work.

Delete orphans examples

Solution 4 - C#

Try changing to

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

because virtual is needed to get lazy loading. as explained here

I think your parent.Children.clear isnt working because the Children have not been loaded

Solution 5 - C#

If your object is self-referencing, you can delete both many-to-many and one-to-many children using the method below. Just remember to call db.SaveChanges() afterwards :)

[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);
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDmytroView Question on Stackoverflow
Solution 1 - C#Sam SippeView Answer on Stackoverflow
Solution 2 - C#SlaumaView Answer on Stackoverflow
Solution 3 - C#KonradView Answer on Stackoverflow
Solution 4 - C#KirstenView Answer on Stackoverflow
Solution 5 - C#Matthew HudsonView Answer on Stackoverflow