没有ORM的存储库模式

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

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

这也引发了诸如变更跟踪,删除等function的问题? 所以我认为最终结果是我可以在没有ORM的情况下进行DDD吗?

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

纳比勒

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

没有ORM我可以做DDD吗?

是的,但ORM简化了事情。

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

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

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

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

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

总结一下:

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

编辑回应评论:

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

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

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

private Func> _lazilyGetOrderLines; 

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

 Order order = new Order(this.GetOrderLines); 

这是OrderRepository上的私有方法:

 private IList GetOrderLines(Guid orderId) { //DAL Code here } 

然后在订单行属性可能看起来像:

 public IEnumberable 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)我应该在GetCustomerById方法中加载Orders列表吗?

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

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

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

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

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

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

例:

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

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

 internal sealed class CustomerProxy : Customer { private bool _ordersLoaded = false; public override IList Orders { get { IList orders = new List(); 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为您提供了许多function。

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

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

麦克风