【问题标题】:Repository Pattern without an ORM没有 ORM 的存储库模式
【发布时间】:2011-06-28 10:14:10
【问题描述】:

我在不使用 ORM 的 .NET C# 应用程序中使用存储库模式。但是我遇到的问题是如何填充实体的一对多列表属性。例如如果客户有一个订单列表,即如果 Customer 类有一个名为 Orders 的 List 属性,而我的存储库有一个名为 GetCustomerById 的方法,那么?

  • 我应该在 GetCustomerById 方法中加载订单列表吗?
  • 如果订单本身有另一个列表属性等等怎么办?
  • 如果我想做延迟加载怎么办?我应该在哪里放置代码以在客户中加载 Orders 属性?在 Orders 属性中获取 {} 访问器?但是然后我必须将存储库注入域实体?我认为这不是正确的解决方案。

这也引发了对更改跟踪、删除等功能的疑问?所以我认为最终结果是我可以在没有 ORM 的情况下做 DDD 吗?

但现在我只对延迟加载域实体中的列表属性感兴趣?有什么想法吗?

纳比尔

我假设对于在域驱动设计中不使用 ORM 的任何人来说,这是一个非常常见的问题?有什么想法吗?

【问题讨论】:

  • 听起来你需要一个 ORM。大多数 ORM 都具有这些功能。编写/维护自己的代码是一项巨大的投资。
  • 你在问我可以不用 ORM 来做这件事,而是你尝试制作自己的 ORM。
  • 我和你有同样的问题,我已经在博客上写了solveme.wordpress.com/2009/11/11/… 希望有一天 DDD 社区会发布一个无需任何 ORM 即可工作的示例应用程序

标签: c# orm domain-driven-design repository-pattern


【解决方案1】:

1) 我应该在 GetCustomerById 方法中加载订单列表吗?

将订单映射代码与客户映射代码分开可能是个好主意。如果您要手动编写数据访问代码,则从 GetCustomerById 方法调用该映射模块是您的最佳选择。

2) 如果订单本身有另一个列表属性等等怎么办?

将所有这些放在一起的逻辑必须存在于某个地方;相关的聚合存储库是一个很好的地方。

3) 如果我想做延迟加载怎么办?我将在哪里放置代码以在客户中加载 Orders 属性?在 Orders 属性 get{} 访问器内?但是然后我必须将存储库注入域实体?我认为这不是正确的解决方案。

我见过的最佳解决方案是让您的存储库返回子类域实体(使用类似 Castle DynamicProxy 的东西)——这样您就可以在域模型中保持对持久性的无知。

【讨论】:

  • 嗨 Jeff.. 在阅读了 Castle Dynamic Proxy 之后,这听起来是个好主意.. 但是我似乎找不到任何如何实际使用它的好例子。
【解决方案2】:

我可以在没有 ORM 的情况下做 DDD 吗?

是的,但是 ORM 可以简化事情。

老实说,我认为您的问题与您是否需要 ORM 无关,而是您对数据的思考过多,而不是对 DDD 成功的关键在于行为。就数据模型而言,大多数实体将以某种形式与大多数其他实体有关联,从这个角度来看,您可以遍历模型的所有内容。这就是您的客户和订单的样子,也许是您认为需要延迟加载的原因。但是您需要使用聚合将这些关系分解为行为组。

例如,为什么您将客户聚合建模为具有订单列表?如果答案是“因为客户可以下订单”,那么我不确定您是否处于 DDD 的心态。

有哪些行为需要客户提供订单列表?当您更多地考虑您的域的行为(即在什么时候需要什么数据)时,您可以根据用例对聚合进行建模,并且事情变得更加清晰和容易,因为您只需跟踪一小部分对象的更改在聚合边界。

我怀疑 Customer 应该是没有订单列表的单独聚合,而 Order 应该是带有订单行列表的聚合。如果您需要对客户的每个订单执行操作,请使用 orderRepository.GetOrdersForCustomer(customerID);进行更改,然后使用 orderRespository.Save(order);

关于没有 ORM 的更改跟踪,有多种方法可以做到这一点,例如,订单聚合可能会引发订单存储库正在侦听的已删除订​​单行的事件。当工作单元完成时,这些可以被删除。或者稍微不那么优雅的方法是维护已删除的列表,即您的存储库显然可以读取的 order.DeletedOrderLines。

总结:

  • 我认为您需要更多地考虑行为而不是数据
  • ORM 让变更跟踪变得更轻松,但您可以不使用它,也可以不使用 DDD。

编辑回应评论:

我认为我不会为订单行实施延迟加载。您可能在不需要订单行的情况下对订单执行哪些操作?我怀疑并不多。

但是,当 DDD 的“规则”似乎没有意义时,我并不局限于此,所以……如果在不太可能的情况下,对订单执行了许多操作不需要填充订单行的对象并且通常有大量订单行与订单相关联(两者都必须是真实的,我认为这是一个问题)然后我会这样做:

在订单对象中有这个私有字段:

private Func<Guid, IList<OrderLine>> _lazilyGetOrderLines;

订单存储库将在创建时将其传递给订单:

Order order = new Order(this.GetOrderLines);

这是 OrderRepository 上的私有方法:

private IList<OrderLine> GetOrderLines(Guid orderId)
{
    //DAL Code here

}

那么在订单行中的属性可能如下所示:

public IEnumberable<OrderLine> OrderLines
{ 
    get 
    {
         if (_orderLines == null)
            _orderLines = _lazilyGetOrderLines(this.OrderId);

         return _orderLines;
    }
}

编辑 2

我发现这篇博文与我的解决方案类似,但更优雅:

http://thinkbeforecoding.com/post/2009/02/07/Lazy-load-and-persistence-ignorance

【讨论】:

  • 嗨,David,关于行为和聚合的概念,我完全同意你的看法。我的例子可能不是一个很好的例子,导致你做出这个解释。但是,如果我们考虑一个稍微好一点的带有 OrderLines 的 Order 示例,那么您将如何实现延迟加载?
  • @nabeelfarid 我已经更新了我的答案以包含一个示例,说明如何在没有 ORM 的情况下实现延迟加载。它显然不如使用 ORM 优雅,正如我所说,我认为我不会为您的场景这样做。但它提供了延迟加载,无需订单聚合直接引用存储库。
  • 使用 ODBMS 在没有 ORM 的情况下进行 DDD 会更容易 :)
  • @Stephan Eggermont 是的 - 我正在开发一个使用 CQRS 和事件源的项目......不需要数据访问代码!太棒了:)
  • 我在stackoverflow.com/questions/20739446/…中提到了你的想法
【解决方案3】:

另一个可能的答案是创建一个继承自 Customer 的新 Proxy 对象,将其命名为 CustomerProxy,并在那里处理延迟加载。这都是伪代码,所以是给大家一个思路,而不是复制粘贴使用。

例子:

public class Customer
{
    public id {get; set;}
    public name {get; set;}
    etc...
    public virtual IList<Order> Orders {get; protected set;}
}

这里是客户“代理”类...这个类并不存在于业务层,而是与您的上下文和数据映射器一起存在于数据层中。请注意,您想要进行延迟加载的任何集合都应声明为虚拟(我相信 EF 4.0 还要求您将 props 设为虚拟,就好像在运行时在纯 POCO 上启动代理类,以便 Context 可以跟踪更改)

internal sealed class CustomerProxy : Customer
{
   private bool _ordersLoaded = false;
    public override IList<Order> Orders
    {
        get
        {
            IList<Order> orders = new List<Order>();
            if (!_ordersLoaded)
            {
                //assuming you are using mappers to translate entities to db and back
                //mappers also live in the data layer
                CustomerDataMapper mapper = new CustomerDataMapper();
                orders = mapper.GetOrdersByCustomerID(this.ID);
                _ordersLoaded = true;

                // Cache Cases for later use of the instance
                base.Orders = orders;
            }
            else
            {
                orders = base.Orders;
            }
            return orders;
        }
   }
}

因此,在这种情况下,我们的实体对象 Customer 仍然不受数据库/数据映射器代码调用的影响,这正是我们想要的……“纯” POCO。您已将延迟加载委托给位于数据层中的代理对象,并实例化数据映射器并进行调用。

这种方法有一个缺点,即调用客户端代码不能覆盖延迟加载...它可以打开或关闭。因此,这取决于您的特定使用情况。如果您知道可能有 75% 的时间总是需要客户的订单,那么延迟加载可能不是最好的选择。在您获得 Customer 实体时,最好让您的 CustomerDataMapper 填充该集合。

再次,我认为 NHibernate 和 EF 4.0 都允许您在运行时更改延迟加载特性,因此,按照惯例,使用 ORM 是有意义的,b/ca 为您提供了很多功能。

如果您不经常使用 Orders,则使用延迟加载来填充 Orders 集合。

我希望这是“正确的”,并且是一种为领域模型设计正确完成延迟加载的方法。我还是这个东西的新手......

迈克

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-13
    • 1970-01-01
    • 2014-03-17
    • 1970-01-01
    相关资源
    最近更新 更多