从集合中删除数据库中的项目(EF 4.3)
在我的EF4.3代码中(但是与EF生成的数据库相比,显式设计数据库)存在以下问题。从集合中删除数据库中的项目(EF 4.3)
我有一个实体“WorkPlan”,它可以包含一对多的“休息”实体。在模型中,工作计划具有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,但该字段不可为空。
添加以下到我的数据方面:
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”AssociationSet的关系处于 “已删除”状态。考虑到多重性限制,相应的 “WorkPlan_Breaks_Target”也必须处于“已删除”状态。
有没有一种简单的方法来实现它?
我确实想出了一个解决方案。但我更喜欢那些纯粹配置的东西。
但它至少隔离到我的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步:找出是否WorkPlan更改是由于新对象。这可能意味着一个新实例化的对象,或从数据存储加载的对象。
第3步:查看添加对象的WorkPlan.Breaks集合。如果该集合是一个EntityCollection <>,则该对象是从数据库加载的。连接AssociationChanged事件以在关联更改时得到通知。
步骤4:收到关联更改事件时,检查它是否为“Remove”事件,如果是,则明确从Breaks DbSet中删除Break。
更简单的解决方案是受欢迎的。
我有同样的问题,经过1.5小时的搜索后,我找到了解决方案。 完成此任务的关键是所谓的“识别关系”。这些是告诉EF该实体只作为某个其他实体的子女“生活”的方式,因此将其从其父母中移除也应该将其从数据库中移除。请参阅相关的问题:
- Is it possible to remove child from collection and resolve issues on SaveChanges? (我实际上标志着这是重复的,但我不知道如何:))
- What's the difference between identifying and non-identifying relationships?
综上所述,我的解决办法是这样的:
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自动生成数据库)。