EF 代码优先:如何在遵循 DDD 时从实体的集合中删除一行?
Posted
技术标签:
【中文标题】EF 代码优先:如何在遵循 DDD 时从实体的集合中删除一行?【英文标题】:EF code first: How to delete a row from an entity's Collection while following DDD? 【发布时间】:2012-01-11 17:07:07 【问题描述】:所以这是场景:
DDD 声明您使用存储库来获取聚合根,然后使用它来添加/删除它拥有的任何集合。
添加很简单,您只需在要添加的Collection
上调用.Add(Item item)
。保存时会在数据库中添加一个新行。但是,删除是不同的——调用.Remove(Item item)
不会从数据库中删除该项目,它只是删除外键。所以虽然,是的,它在技术上不再是集合的一部分,但它仍然在数据库中。
仔细阅读,唯一的解决方案是使用数据上下文将其删除。但根据 DDD,域对象不应该知道数据上下文,因此必须在域之外进行删除。
解决这个问题的正确方法是什么?或者是否可以让数据库充满孤儿(也许运行例程来清除它们)?
【问题讨论】:
【参考方案1】:我已经通过使用domain events 在我目前正在处理的应用程序中解决了这个问题;一个 DDD 概念Eric Evans said 应该在他的书中。
虽然不允许域对象了解对象上下文,但 IDomainEventHandler
是 - 因此我有一个 DomainObjectDeletionHandler
,它会在控制返回到我的应用程序层之前从对象上下文中删除“已删除”的对象,并且更改已保存。
如需更多信息,我已写信 a blog 介绍我的领域事件实现以及我如何将所有内容连接在一起。
希望有帮助:)
编辑
例如,如果您有一个 Order
类,其中有一个 OrderItems
类型为 OrderItem
的集合:
public class Order
// Other stuff
public void RemoveOrderItem(int orderItemId)
var orderItemToRemove = OrderItems.First(oi => oi.Id == orderItemId)
OrderItems.Remove(orderItemToRemove);
DomainEvents.Raise(new OrderItemRemoved(orderItemToRemove));
【讨论】:
你究竟什么时候会引发删除事件? 从域对象上的方法进行删除 - 我已经更新了答案。【参考方案2】:从集合中删除子实体时,EF 会将其保留为孤儿,仅删除外键。
如果您不想使用 DbContext 显式删除它,您可以使用所谓的“识别关系”(底部的http://msdn.microsoft.com/en-us/library/ee373856.aspx)。
诀窍是在子级上设置一个复合主键,包括父级的主键。
一旦你这样做了,当从父集合中删除实体时,它也会从表中删除。
【讨论】:
这个答案被低估了。我最初也担心 EF 在“孤立”删除的实体上的行为(或者在外键约束的情况下,它根本无法继续),但是如上所述配置复合键确实会导致这些实体从数据库中物理删除当您删除它们时。我喜欢其他一些建议,例如使用域事件,但似乎只是更改密钥更简单。【参考方案3】:我不知道这是否是设计使然,但是如果一个详细对象有一个包含其主对象的键列的复合键,那么如果您将它从主对象的集合中删除,它将被自动删除。如果您有一个带有 OrderID 键和 ICollection OrderLines 导航属性的 Order 对象,请为 OrderLine 提供一个包含 OrderID 和 OrderLineID 的复合键。
但是由于我不知道我是否可以依赖它,我自己使用的解决方案是让 EF 以它的方式处理它,并在调用 SaveChanges(),枚举所有修改过的实体并根据需要将状态更改为已删除。
【讨论】:
在 EF 方面称为identifying relation。【参考方案4】:我通过根据需要配置引用列并将删除行为配置为Cascade
解决了这种情况
例子:
modelBuilder.Entity<AggregateRoot>()
.HasMany(x => x.Items)
.WithOne()
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
在这种情况下,EF Core (6.x) 不再将引用列设置为 NULL
,而是通过从聚合根的 Items
集合中删除 Item
来删除记录。
这里的决定性配置是删除行为Cascade
。
【讨论】:
【参考方案5】:为什么不使用两个存储库?
var parent = ParentRepo.Get(parentId);
parent.Children.Remove(childId); // remove it from the property Collection
ChildRepo.Delete(childId); // delete it from the database
ParentRepo.Commit(); // calls underlying context.SaveChanges()
假设您通过 IOC/DI 共享上下文,使用一个 repo 调用 commit 将同时提交,否则也只需调用 ChildRepo.Commit
。
【讨论】:
以上是关于EF 代码优先:如何在遵循 DDD 时从实体的集合中删除一行?的主要内容,如果未能解决你的问题,请参考以下文章
EF 代码优先 - 配置一对零或一关系,无需共享 PK/FK