没有ORM的存储库模式

时间:2011-02-16 14:55:38

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

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

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

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

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

纳比尔

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

3 个答案:

答案 0 :(得分:12)

  

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

是的,但ORM简化了事情。

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

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

哪些行为需要客户拥有订单列表?当您更多地考虑域的行为(即在什么点需要什么数据)时,您可以根据用例对事物进行建模,事情变得更加清晰和简单,因为您只需更改跟踪一小组对象在总边界。

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

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

总结:

  • 我认为你需要更多地考虑行为而不是数据
  • ORM让变更跟踪的生活变得更轻松,但你可以在没有变更跟踪的情况下完成,你绝对可以在没有变更跟踪的情况下进行DDD。

编辑回复评论:

我认为我不会为订单行实施延迟加载。您可以在订单上执行哪些操作而无需订单行?我怀疑的并不多。

然而,当它看起来没有意义时,我不是一个被限制在DDD的'规则'的人,所以...如果在不太可能的情况下,在订单上执行了许多操作不需要填充订单行的对象 AND 通常会有大量与订单关联的订单行(两者都必须为我认为是一个问题)然后我这样做:

在订单对象中包含此私有字段:

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

答案 1 :(得分:4)

  

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

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

  

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

把所有这些放在一起的逻辑必须住在某个地方;相关的聚合存储库与任何地方一样好。

  

3)如果我想做延迟加载怎么办?我会在哪里放置代码来加载客户的Orders属性?在Orders属性中获取{} accessor?但是,我必须将存储库注入域实体?我认为这不是正确的解决方案。

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

答案 2 :(得分:1)

另一个可能的答案是创建一个从Customer继承的新Proxy对象,称之为CustomerProxy,并在那里处理延迟加载。所有这些都是伪代码,所以它给你一个想法,而不仅仅是复制和粘贴它。

示例:

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

这里是Customer“proxy”类...此类不在业务层中,而是与Context和Data Mappers一起存在于数据层中。请注意,您想要延迟加载的任何集合都应该声明为虚拟(我相信EF 4.0还要求您将道具设为虚拟,就好像在纯POCO上运行时在代理类上旋转代码类,以便Context可以跟踪更改)< / p>

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是有意义的,为您提供了很多功能。

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

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

麦克