EF 4.1 CodeFirst - каскадное удаление «один ко многим» - PullRequest
3 голосов
/ 16 марта 2012

Я пытаюсь удалить дочернюю сущность из свойства навигации родительской коллекции. Существует взаимосвязь «один ко многим» между родителем и ребенком. Как только я удаляю ребенка, я хочу, чтобы база данных удалила ассоциацию. дочерняя запись из базы данных, а не осиротение этой записи путем обнуления внешнего ключа.

Есть ли способ сделать это без необходимости явно удалять дочерний элемент через дочерний DbSet в DBContext?

Я видел другие посты, связанные с этой темой, но думал, что перегоню код до более простого теста:

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

Ответы [ 2 ]

5 голосов
/ 16 марта 2012

Есть ли способ сделать это без явного удаления child через дочерний DbSet в DBContext?

В вашей модели: нет, другого пути нет. Вы должны позвонить:

var parent = _context.Parents.FirstOrDefault();
var firstChild = parent.MyChildren.FirstOrDefault();
_context.Children.Remove(firstChild);

_context.SaveChanges();

Недавно я узнал, что есть одно исключение, которое вызывает автоматическое удаление в базе данных при удалении дочернего элемента из родительской коллекции. Это так называемое Идентификационное отношение , которое требует, чтобы свойство внешнего ключа в дочернем элементе, ссылающееся на родителя, было частью (составного) первичного ключа дочернего элемента:

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

В этом случае ваш код действительно удалит ребенка из базы данных. Здесь объясняется (последний раздел внизу): http://msdn.microsoft.com/en-us/library/ee373856.aspx

0 голосов
/ 16 марта 2012
    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();

Для получения дополнительной информации перейдите сюда: https://stackoverflow.com/a/9571108/1241400

РЕДАКТИРОВАТЬ: Почему менее общий

 public void DeleteMany<E>(IQueryable<E> entitiesToDelete) where E : class
        {
            foreach (var entity in entitiesToDelete)
            {
                DataContext.Entry(entity).State = System.Data.EntityState.Deleted;
            }
        } 

и вы просто назовете это

var childrenToDelete = someRepository.FindChildrenByParentId<Children>(ParentId);
someRepository.DeleteMany<Children>(childrenToDelete);
...