【问题标题】: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 聚合根提交到存储库。

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

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

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

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

我是否必须使用 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 加载)?

标签: entity-framework repository domain-driven-design aggregateroot


【解决方案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

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

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-16
  • 2013-02-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多