从聚合根中删除子

Posted

技术标签:

【中文标题】从聚合根中删除子【英文标题】:Delete a child from an aggregate root 【发布时间】:2012-12-13 14:00:45 【问题描述】:

我有一个带有 AddUpdateDelete 的通用存储库。 我们将其命名为 CustomerRepository

我有一个名为 Customer 的实体 (POCO),它是一个聚合根,带有 Addresses

public class Customer

     public Address Addresses  get; set; 

我处于分离实体框架 5 场景中。

现在,假设在获得客户后,我选择删除客户地址。 我通过 Update 方法将 Customer 聚合根提交到存储库。

如何保存对地址所做的修改?

    如果地址id为0,我可以假设地址是新的。 对于地址的其余部分,我可以选择附加所有地址,并标记为已更新。 对于已删除的地址,我看不到任何解决方法...

我们可以说这个解决方案是不完整且低效的。

那么聚合根子节点应该如何更新呢?

我是否必须使用 AddAddressUpdateAddressDeleteAddress 等方法来完成 CustomerRepository

这似乎会打破这种模式......

我是否在每个 POCO 上设置了 Persistence 状态:

public enum PersistanceState

     Unchanged,
     New,
     Updated,
     Deleted

然后在我的 CustomerRepository 中只有一种方法,Save

在这种情况下,我似乎正在重新发明实体“非 POCO”对象,并将数据访问相关属性添加到业务对象......

【问题讨论】:

这些是 EF 实体吗?如果我错了,请纠正我,但是如果您加载聚合根(Customer)并访问其导航属性(即Addresses),这些不应该被延迟加载,附加到上下文并因此被保存/调用SaveChanges时自动删除? 在我的例子中,客户是分离的,然后是附加的,而不是从 DbContext 加载的。 Poco 实体完全独立于 EF,不可能有延迟加载。 好的,所以您的意思是在您的存储库中,您加载客户(ENTITY POCO)并将其映射到 DOMAIN POCOCustomer?然后在你想把它映射回一个 ENTITY POCO 并保存之前对它做一些操作? 不,POCO 是实体,这要归功于 POCO T4 模板。 天啊...您是否使用代理(从 DbContext 加载)? 【参考方案1】:

首先,您应该使用 Add、Update 和 Delete 方法来保留您的存储库,尽管我个人更喜欢 Add、indexer set 和 Remove,以便使存储库看起来像应用程序代码的内存集合。

其次,存储库应负责跟踪持久性状态。我什至不会用

弄乱我的域对象
object ID  get; 

就像有些人一样。相反,我的存储库如下所示:

public class ConcreteRepository : List<AggregateRootDataModel>, IAggregateRootRepository

AggregateRootDataModel 类用于跟踪我的内存中对象的 ID 以及跟踪任何持久性信息。在你的情况下,我会放置一个属性

List<AddressDataModel> Addresses  get; 

在我的 CustomerDataModel 类上,该类还将包含客户域对象以及客户的数据库 ID。然后,当客户更新时,我会有如下代码:

public class ConcreteRepository : List<AggregateRootDataModel>, IAggregateRootRepository

    public Customer this[int index]
    
        set
        
            //Lookup the data model
            AggregateRootDataModel model = (from AggregateRootDataModel dm in this
                                           where dm.Customer == value
                                           select dm).SingleOrDefault();
            //Inside the setter for this property, run your comparison 
            //and mark addresses as needing to be added, updated, or deleted.
            model.Customer = value;
            SaveModel(model); //Run your EF code to save the model back to the database.
        
    

这种方法的主要警告是您的域模型必须是引用类型,并且您不应该覆盖 GetHashCode()。这样做的主要原因是,当您执行匹配数据模型的查找时,哈希码不能依赖于任何可更改属性的值,因为即使应用程序代码修改了值,它也需要保持不变域模型实例的属性。使用这种方法,应用程序代码变为:

IAggregateRootRepository rep = new ConcreteRepository([arguments that load the repository from the db]);
Customer customer = rep[0]; //or however you choose to select your Customer.
customer.Addresses = newAddresses;  //change the addresses
rep[0] = customer;

【讨论】:

在 n 层方案中,具体存储库是您的数据层,将位于您的 DAL 项目中。 Customer 对象是您的应用程序层的一部分,将在您的应用程序项目中。调用客户对象的代码也是应用层的一部分。话虽如此,您可以从使用 DDD 的洋葱架构中获得更多好处。您还可以在物理部署环境中获得更大的灵活性。洋葱架构的整个想法是您的业务逻辑(客户对象)不依赖任何东西。这样,客户对象就独立于部署场景。 Onion Architecture 这是一篇关于 Onion 架构的好文章。您还可以从这篇文章链接到最初创造该术语的 Jeffrey Palermo 的博客。 但是您正在使用对象引用来比较实体,或者在 n-itier 中,对象被序列化/反序列化,实体在旅行时会丢失其引用。您还必须处于有状态的场景中。 您不应该序列化/反序列化域实体。如果您的物理架构意味着表示层与应用程序层位于不同的机器上,那么您应该使用 DTO 和服务调用将您的表示关注点传达回应用程序层。在这种情况下,每个服务调用都处理该服务调用期间对象的状态。例如,如果您有一个服务方法 SaveCustomer(CustomerDTO),那么该服务方法将查看域实体是否存在,并更新现有实体或添加新实体。 明确地说,每个上下文都是一个有状态的场景。即使您在 Web 服务器上编写表示层,每个回发都是它自己的状态。在 Web 环境中,您无法避免必须加载存储库、向用户显示对象以及在用户进行更改时必须重新加载存储库。但是,您可以跟踪用户正在更改的内容,因此您只需在每个回帖上加载必要的对象。这样,在回发时,您只需将用户更改或要求对其执行某些操作的对象加载到存储库。【参考方案2】:

简单的方法是使用自我跟踪实体What is the purpose of self tracking entities?(我不喜欢它,因为跟踪是不同的责任)。

困难的方法是,你拿原始集合然后比较:-/

Update relationships when saving changes of EF4 POCO objects

其他方式可能是事件跟踪?

【讨论】:

我不会使用 STE,因为它会打败我的 POCO 方法,而且微软现在不再推荐它 (msdn.microsoft.com/en-us/data/jj613668)。看来我必须加载我的聚合根并手动将其与分离的根进行比较... 我过去做过类似的事情:***.com/questions/6125871/…

以上是关于从聚合根中删除子的主要内容,如果未能解决你的问题,请参考以下文章

删除 XML 命名空间,同时忽略根中的命名空间

聚合根中覆盖的实体如何保存在 DDD 中?

从聚合管道中的集合中减去子文档

从聚合中批量删除文档

使用聚合函数从 MySql 表中删除?

MongoDB Mongoose 聚合查询深度嵌套数组删除空结果并填充引用