2012-07-18 83 views
1

在我的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”也必须处于“已删除”状态。

有没有一种简单的方法来实现它?

回答

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步:找出是否WorkPlan更改是由于新对象。这可能意味着一个新实例化的对象,或从数据存储加载的对象。

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

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

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

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自动生成数据库)。