Entity framework 从集合中删除时删除数据库中的项目(EF 4.3)

Entity framework 从集合中删除时删除数据库中的项目(EF 4.3),entity-framework,Entity Framework,首先在我的EF4.3代码中(但是使用显式设计的数据库,而不是EF生成的数据库),我有以下问题 我有一个实体“工作计划”,它可以包含一到多个“中断”实体。在模型中,工作计划有一个ICollection,但中断不知道该工作计划 这是一种聚合关系。“中断”不能存在于工作计划范围之外 我希望发生的是,当我从工作计划的中断集合中删除中断时,在保存更改时,应在数据库中删除该中断: [Test] public void ShouldRemoveBreakInDatabase() { // Setup

首先在我的EF4.3代码中(但是使用显式设计的数据库,而不是EF生成的数据库),我有以下问题

我有一个实体“工作计划”,它可以包含一到多个“中断”实体。在模型中,工作计划有一个ICollection,但中断不知道该工作计划

这是一种聚合关系。“中断”不能存在于工作计划范围之外

我希望发生的是,当我从工作计划的中断集合中删除中断时,在保存更改时,应在数据库中删除该中断:

[Test]
public void ShouldRemoveBreakInDatabase()
{
    // Setup
    var workPlan = WorkPlanBuilder.Build(x => x.AddBreak());
    Save(workPlan);

    // Exercise
    var exerciseContext = CreateDataContext();
    workPlan = exerciseContext.WorkPlans.Single();
    workPlan.RemoveBreak(workPlan.Breaks.Single());
    exerciseContext.SaveChanges();

    // Verify            
    var actual = SqlHelper.ExecuteScalar("select count(*) from Breaks");
    Assert.That(actual, Is.EqualTo(0));
}
但是,SaveChanges()调用会导致以下异常:

System.Data.Entity.Infrastructure.DbUpdateException:错误 保存不公开外键的实体时发生 属性的关系。EntityEntries属性将 返回null,因为无法将单个实体标识为源 这是个例外。可以在保存时处理异常 通过在实体类型中公开外键属性更容易。看见 有关详细信息,请参阅InnerException。
---->System.Data.UpdateException: 更新条目时出错。查看内部异常 有关详细信息。
---->System.Data.SqlClient.SqlException:无法 将值NULL插入表的“工作计划Id”列中 'ActivityStore.dbo.Breaks';列不允许空值。更新失败。 声明已终止

显然,当从工作计划集合中删除中断时,EF假定它应该在数据库中将工作计划Id字段设置为null,但该字段不能为null

将以下内容添加到我的数据上下文中:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<WorkPlan>().HasMany(x => x.Breaks).WithRequired();
}
模型创建时受保护的覆盖无效(DbModelBuilder modelBuilder)
{
modelBuilder.Entity()有许多(x=>x.Breaks).WithRequired();
}
导致不同的异常:

System.Data.Entity.Infrastructure.DbUpdateException:错误 保存不公开外键的实体时发生 属性的关系。EntityEntries属性将 返回null,因为无法将单个实体标识为源 这是个例外。可以在保存时处理异常 通过在实体类型中公开外键属性更容易。看见 有关详细信息,请参阅InnerException。
---->System.Data.UpdateException: “工作计划中断”关联的关系在 “已删除”状态。给定多重性约束,相应的 “工作计划目标”也必须处于“已删除”状态


有没有一个简单的方法可以解决这个问题?

我确实想出了一个解决方案。但我更喜欢纯配置的东西,而不是这个

但它至少与我的DataContext实现是隔离的:

    private DbSet<WorkPlan> _workPlans;
    public DbSet<WorkPlan> WorkPlans
    {
        get { return _workPlans; }
        set 
        { 
            _workPlans = value;
            _workPlans.Local.CollectionChanged += LocalWorkPlansChanged;
        }
    }

    private void LocalWorkPlansChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.Action != NotifyCollectionChangedAction.Add)
            return;

        foreach (var workPlan in e.NewItems.Cast<WorkPlan>())
        {
            var collection = workPlan.Breaks as EntityCollection<Break>;
            if (collection == null)
                continue;
            collection.AssociationChanged += WorkPlanBreaksAssociationChanged;
        }
    }

    private void WorkPlanBreaksAssociationChanged(object sender, CollectionChangeEventArgs e)
    {
        if (e.Action == CollectionChangeAction.Remove)
        {
            var @break = (Break)e.Element;
            Breaks.Remove(@break);
        }
    }
私人数据库集工作计划;
公共数据库集工作计划
{
获取{返回_工作计划;}
设置
{ 
_工作计划=价值;
_workPlans.Local.CollectionChanged+=LocalWorkBanChanged;
}
}
私有void LocalWorkpanChanged(对象发送方,NotifyCollectionChangedEventArgs e)
{
if(e.Action!=NotifyCollectionChangedAction.Add)
回来
foreach(e.NewItems.Cast()中的var工作计划)
{
var collection=工作计划。中断为EntityCollection;
if(集合==null)
持续
collection.AssociationChanged+=WorkBanbreaksAssociationChanged;
}
}
private void WorkBanbreaksAssociationChanged(对象发送方,CollectionChangeEventArgs e)
{
if(e.Action==CollectionChangeAction.Remove)
{
var@break=(break)e.Element;
断开。移除(@break);
}
}
所以

步骤1:连接在DbSet的内存中工作计划集合更改时引发的事件

步骤2:确定工作计划更改是否是由于新对象引起的。这可能意味着新实例化的对象,或从数据存储加载的对象

步骤3:查看工作计划。断开添加对象的集合。如果集合是EntityCollection,则该对象是从数据库加载的。连接AssociationChanged事件以在关联更改时获得通知

步骤4:当接收到关联更改事件时,检查它是否为“移除”事件,如果是,则显式地从中断数据集中移除中断


更简单的解决方案是受欢迎的。

我也有同样的问题,在谷歌搜索了1.5个小时后,我找到了解决方案。 完成这项任务的关键是所谓的“确定关系”。这是告诉EF实体只作为其他实体的子实体“存在”的方法,所以从其父实体中删除它也应该从数据库中删除它。见相关问题:

  • (我实际上会将此标记为重复,但我不知道如何:))
总之,我的解决方案如下所示:

public class Item
{
    [Key, Column(Order = 0), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    [Key, ForeignKey("Group"), Column(Order = 1)]
    public int GroupId { get; set; }
    public ItemGroup Group { get; set; }
}

public class ItemGroup
{
    public int Id { get; set; }
    public ICollection<Item> Items { get; set; }
}
公共类项目
{
[键,列(顺序=0),DatabaseGenerated(DatabaseGenerateOption.Identity)]
公共int Id{get;set;}
[键,外键(“组”),列(顺序=1)]
public int GroupId{get;set;}
公共项目组{get;set;}
}
公共类项目组
{
公共int Id{get;set;}
公共ICollection项{get;set;}
}
在database Item.Id是and identity列中,GroupId是一个外键(我不会用EF自动生成db)