Reputation: 1825
I'm trying to remove a child entity from a parent collection navigation property. There is a one-to-many relationship set up b/t parent and child. Once I remove the child, I want the database to remove the assoc. child record from the database rather than orphaning that record by nullifying the foreign key.
Is there any way to do this without having to explicitly delete the child via the child DbSet in the DBContext?
I've seen other posts related to this topic, but I thought I'd distill the code down to a simpler test case:
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using NUnit.Framework;
namespace basic_tests
{
[TestFixture]
public class OneToManyTests
{
#region Setup/Teardown
[SetUp]
public void SetUp()
{
_context = new Context();
Database.SetInitializer(new DataInitializer());
}
#endregion
private Context _context;
[Test]
public void CanRemoveChildThroughParent()
{
/**
this throws : "System.Data.Entity.Infrastructure.DbUpdateException : An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for
details.System.Data.UpdateException : A relationship from the 'Child_MyParent' AssociationSet is in the 'Deleted' state. Given multiplicity constraints, a
corresponding 'Child_MyParent_Source' must also in the 'Deleted' state.
**/
var parent = _context.Parents.FirstOrDefault();
var firstChild = parent.MyChildren.FirstOrDefault();
parent.MyChildren.Remove(firstChild);
_context.SaveChanges();
var parentRefresh = new Context().Parents.FirstOrDefault();
Assert.AreEqual(1, parentRefresh.MyChildren.Count);
var childrenCount = new Context().Children.Count();
Assert.AreEqual(1, childrenCount);
}
}
public class Parent
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<Child> MyChildren { get; set; }
}
public class Child
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Parent MyParent { get; set; }
}
public class Context : DbContext
{
public DbSet<Parent> Parents { get; set; }
public DbSet<Child> Children { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Child>()
.HasRequired(c => c.MyParent)
.WithMany(p => p.MyChildren)
.WillCascadeOnDelete(true);
}
}
public class DataInitializer : DropCreateDatabaseAlways<Context>
{
protected override void Seed(Context context)
{
for (var i = 0; i < 2; i++)
{
context.Children.Add(new Child
{
Name = "child" + i
});
}
var parent = new Parent { Name = "parent", MyChildren = context.Children.Local.ToList() };
context.Parents.Add(parent);
base.Seed(context);
}
}
}
Upvotes: 3
Views: 3792
Reputation: 13391
var parent = context.Parents.Include(p=>p.Children).Where(p=>p.ParentId == 1);
foreach(Children child in parent.Children.ToList())
{
context.Entry(child).State = EntityState.Deleted;
}
context.Entry(parent).State = EntityState.Deleted;
context.SaveChanges();
For more info go here: https://stackoverflow.com/a/9571108/1241400
EDIT: Why less generic
public void DeleteMany<E>(IQueryable<E> entitiesToDelete) where E : class
{
foreach (var entity in entitiesToDelete)
{
DataContext.Entry(entity).State = System.Data.EntityState.Deleted;
}
}
and you'll just call it
var childrenToDelete = someRepository.FindChildrenByParentId<Children>(ParentId);
someRepository.DeleteMany<Children>(childrenToDelete);
Upvotes: 0
Reputation: 177163
Is there any way to do this without having to explicitly delete the child via the child DbSet in the DBContext?
In your model: No, there is no other way. You have to call:
var parent = _context.Parents.FirstOrDefault();
var firstChild = parent.MyChildren.FirstOrDefault();
_context.Children.Remove(firstChild);
_context.SaveChanges();
I recently have learned that there is one exception which causes an automatic delete in the database when you remove the child from the parent collection. That's the so-called Identifying relationship which requires that the foreign key property in the child refering to the parent must be part of the (composite) primary key of the child:
public class Child
{
[Key, Column(Order = 0)]
public virtual int Id { get; set; }
[Key, ForeignKey("MyParent"), Column(Order = 1)]
public virtual int MyParentId { get; set; }
public virtual string Name { get; set; }
public virtual Parent MyParent { get; set; }
}
In this case your code would indeed delete the child from the database. It is explained here (last section at the bottom): http://msdn.microsoft.com/en-us/library/ee373856.aspx
Upvotes: 5