1

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

我有一个实体“WorkPlan”,它可以包含一对多的“Break”实体。在模型中,工作计划有一个 ICollection,但 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,但该字段不可为 null。

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

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

导致不同的异常:

System.Data.Entity.Infrastructure.DbUpdateException :保存不为其关系公开外键属性的实体时发生错误。EntityEntries 属性将返回 null,因为无法将单个实体标识为异常源。通过在实体类型中公开外键属性,可以更轻松地在保存时处理异常。有关详细信息,请参阅 InnerException。
----> System.Data.UpdateException:来自“WorkPlan_Breaks”关联集的关系处于“已删除”状态。给定多重约束,相应的“WorkPlan_Breaks_Target”也必须处于“已删除”状态。

有没有一种简单的方法可以做到这一点?

4

2 回答 2

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 自动生成 db)。

于 2013-03-26T15:18:20.307 回答
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 的内存中工作计划集合更改时引发的事件。

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

第 3 步:查看添加对象的 WorkPlan.Breaks 集合。如果集合是 EntityCollection<>,则该对象是从数据库中加载的。连接 AssociationChanged 事件以在关联更改时获得通知。

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

欢迎使用更简单的解决方案。

于 2012-07-18T09:10:38.223 回答