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

时间:2012-07-18 08:12:38

标签: entity-framework

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

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

这是一种聚合关系。 “休息”不能超出工作计划的范围。

我想要发生的是,当我从WorkPlan的休息集合中删除Break时,在保存更改时应该在数据库中删除该中断:

[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插入到'WorkPlan_Id'列中   'ActivityStore.dbo.Breaks';列不允许空值。更新失败。   声明已经终止。

很明显,当从WorkPlan的集合中删除Break时,EF假定它应该在数据库中将WorkPlan_Id字段设置为null,但该字段不可为空。

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

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<WorkPlan>().HasMany(x => x.Breaks).WithRequired();
}

导致另一个异常:

  

System.Data.Entity.Infrastructure.DbUpdateException:错误   保存不公开外键的实体时发生   他们关系的属性。 EntityEntries属性将   返回null,因为无法将单个实体标识为源   例外。可以在保存时处理异常   通过在实体类型中公开外键属性更容易。看到   InnerException以获取详细信息   ----&GT; System.Data.UpdateException:   来自'WorkPlan_Breaks'AssociationSet的关系在   '删除'状态。给定多重约束,相应的   'WorkPlan_Breaks_Target'也必须处于'已删除'状态。

有一种简单的方法可以实现吗?

2 个答案:

答案 0 :(得分:0)

我确实想出了一个解决方案。但我更喜欢纯粹配置的东西。

但它至少与我的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);
        }
    }

所以,

步骤1:每当DbSet的内存中WorkPlans集合发生更改时,就会引发上升的事件。

步骤2:确定WorkPlan更改是否是因为新对象。这可能意味着新实例化的对象,或者从数据存储中加载的对象。

步骤3:查看WorkPlan.Breaks集合以了解添加的对象。如果集合是EntityCollection&lt;&gt;,则从数据库加载对象。连接AssociationChanged事件以在关联更改时收到通知。

步骤4:收到关联更改事件时,检查它是否为“删除”事件,如果是,则从Breaks DbSet中明确删除Break。

欢迎更简单的解决方案。

答案 1 :(得分:0)

我遇到了同样的问题,经过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; }
}

在数据库Item.Id是和标识列中,GroupId是外键(我不会自动使用EF生成数据库)。