首先在我的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'也必须处于'已删除'状态。
有一种简单的方法可以实现吗?
答案 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生成数据库)。