从集合中删除子项并在保存更改时解决问题是否可行?

22
我们正在使用带有外键关系的Entity Framework Code First。我们正在研究如何处理在应用程序中从实体ICollection中删除对象的方法。
当我们有一个具有子关系的实体时,我们可以直接使用Add方法将对象添加到其ICollection中。现在当您使用remove时,会出现错误:
System.InvalidOperationException发生的消息=操作失败:无法更改关系,因为一个或多个外键属性是非可空的。当对关系进行更改时,相关联的外键属性将设置为null值。如果外键不支持null值,则必须定义新关系,将外键属性分配给另一个非null值或删除无关对象。
我了解这是因为从集合上的Remove仅通过设置外键为空来删除关系。我们希望在实体中编写业务逻辑并允许移除。
所以,从它的Repostiory中获取根实体,例如从OrderRepository获取Order,然后调用实体的某些特定方法,例如Order.AddOrderline(Orderline orderline)。这将向订单的virtual ICollection OrderLines添加一个OrderLine。
但是,我们不能像Order.CancelOrderline(int orderLineId)这样编写代码,因为仅从ICollection中删除会导致保存更改时出错。显然,我们可以直接从上下文中进行删除。但是,我想使其成为实体的一部分。是否可以在Entity Framework的SaveChanges事件中清理没有外键的某些实体?显然需要告诉EF可以删除哪些实体,如果它们具有空外键。
我们目前正在使用存储库模式,因此控制器无法访问上下文。我显然可以使用OrderLine存储库或Order存储库中的remove OrderLine方法。但是,只是想知道是否可能在实体上编写代码,而不引用持久性机制。你怎么看?我们做错了吗?其他ORM允许您仅从子集合中删除吗?
2个回答

40

我不知道以下内容是否适用于您,但是Entity Framework支持标识关系。在这种关系中,子实体(从属)到父实体(主要)的外键必须是子实体的(组合)主键的一部分。例如 - 使用DbContext数据注释 - 您的模型类应如下所示:

public class Order
{
    [Key]
    public int OrderId { get; set; }

    public ICollection<OrderLine> OrderLines { get; set; }
}

public class OrderLine
{
    [Key, ForeignKey("Order"), Column(Order = 1)]
    public int OrderId { get; set; }

    [Key, Column(Order = 2)]
    public int OrderLineId { get; set; }

    public Order Order { get; set; }
}

如果你想的话,可以将OrderLineId设为自动生成的标识符。重要的是,Order的外键是主键的一部分。

例如,以下代码就可以实现此目的...

using (var ctx = new MyContext())
{
    var order = ctx.Orders.Include("OrderLines").Single(o => o.OrderId == 1);
    var orderLineToDelete = order.OrderLines
        .FirstOrDefault(ol => ol.OrderLineId == 5);
    if (orderLineToDelete != null)
        order.OrderLines.Remove(orderLineToDelete);

    ctx.SaveChanges();
}

...将确实从数据库中删除orderLineToDelete

有关"识别和非识别关系的注意事项"部分的更多详细信息,请参见此处


啊,所以如果我在子实体上创建一个复合键,EF会为我处理这个问题。看起来很有吸引力。 - GraemeMiller
6
很不错,但我需要添加 [Key, Column(Order = 0), DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int OrderLineId { get; set; } 来防止它抱怨标识插入错误。 - GraemeMiller
1
感谢您的帮助。我现在非常喜欢能够使用从集合中删除元素,因为它使我的代码更加简洁。奇怪的是,在我阅读的有关 EF 和从集合中删除元素的文章中,这似乎并没有经常被提及。很多时候人们只是说你不能从集合中删除元素。我现在会记得告诉别人这个方法! - GraemeMiller
1
@Kulvis:这是不可能的,因为你的关系是可选的,所以你的外键必须允许 NULL。但在识别关系中,FK 必须是 PK 的一部分,而且在 PK 中不能允许 NULL。如果关系是必需的(WithRequired),则必须为 OrderLine 定义一个复合 PK:modelBuilder.Entity<OrderLine>().HasKey(ol => new { ol.OrderId, ol.OrderLineId }); - Slauma
2
我简直不敢相信我之前不知道这个!谢谢。我必须说,EF 在处理这种类型的问题时非常令人困惑,而且错误信息非常差(隐藏在三层内部异常中)。顺便说一下,我和 @GraemeMiller 有类似的问题,但是我很流利,所以解决方案是:this.HasKey(c => new { c.ChildPrimaryKey, c.ParentPrimaryKey }); 以及 this.Property(c => c.ChildPrimaryKey).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); - David Masters
显示剩余3条评论

2

如您所发现的那样,如果您只是从集合中删除一个实体,则该实体仍然附加在对象上下文中,并在调用SaveChanges()时会给您带来错误;我使用领域事件来实现通过存储库从对象上下文中清除实体的整洁方式。

我在我的回答中详细说明了这种方法


那看起来是一个非常有趣的方法。它是否类似于命令模式?我喜欢建模领域事件的概念。我真的需要完成阅读我的蓝皮书。 - GraemeMiller
1
嗯...不完全是命令,我认为——领域事件是没有行为的对象,它们向处理它们的任何对象发出信号,表明已经发生了某些事情。我想我最初是从Martin Fowler那里读到有关它们的内容的,但这篇文章真正帮助我理解了它们的强大之处 :) - Steve Wilkes
很好,正如你所说,它们没有有效载荷,这很有道理。我会阅读相关资料的。感谢提供额外的参考。 - GraemeMiller

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接