没有 ORM 的存储库模式
我在不使用 ORM 的 .NET C# 应用程序中使用存储库模式。然而,我遇到的问题是如何填充实体的一对多列表属性。例如,如果客户有一个订单列表,即如果 Customer 类有一个名为 Orders 的 List 属性,并且我的存储库有一个名为 GetCustomerById 的方法,那么呢?
- 我应该在 GetCustomerById 方法中加载订单列表吗?
- 如果 Order 本身有另一个列表属性等等怎么办?
- 如果我想延迟加载怎么办?我应该在哪里放置代码来加载客户中的 Orders 属性?在 Orders 属性内部 get{} 访问器?但随后我必须将存储库注入域实体中?我认为这不是正确的解决方案。
这也引发了对更改跟踪、删除等功能的疑问?所以我认为最终的结果是我可以在没有 ORM 的情况下进行 DDD 吗?
但现在我只对我的域实体中的延迟加载列表属性感兴趣?有什么想法吗?
Nabeel
我假设对于在域驱动设计中没有使用 ORM 的人来说这是一个非常常见的问题?有什么想法吗?
I am using repository pattern in a .NET C# application that does not use an ORM. However the issue I am having is how to fill One-to-many List properties of an entity. e.g. if a customer has a list of orders i.e. if the Customer class has a List property called Orders and my repository has a method called GetCustomerById, then?
- Should I load the Orders list within the GetCustomerById method?
- What if the Order itself has another list property and so on?
- What if I want to do lazy loading? Where would I put the code to load the Orders property in customer? Inside the Orders property get{} accessor? But then I would have to inject repository into the domain entity? which I don't think is the right solution.
This also raises questions for Features like Change Tracking, Deleting etc? So i think the end result is can I do DDD without ORM ?
But right now I am only interested in lazy loading List properties in my domain entities? Any idea?
Nabeel
I am assuming this is a very common issue for anyone not using an ORM in a Domain Driven Design? Any idea?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
是的,但是 ORM 简化了事情。
老实说,我认为你的问题与是否需要 ORM 无关 - 而是你对数据思考太多,而不是行为,而行为是 DDD 成功的关键。就数据模型而言,大多数实体将以某种形式与大多数其他实体有关联,从这个角度来看,您可以遍历模型的所有部分。这就是您的客户和订单的样子,也许也是您认为需要延迟加载的原因。但是您需要使用聚合将这些关系分解为行为组。
例如,为什么您要对客户聚合进行建模以获取订单列表?如果答案是“因为客户可以下订单”,那么我不确定您是否处于 DDD 思维模式。
有什么行为要求客户拥有订单列表?当您更多地考虑域的行为(即在什么时候需要什么数据)时,您可以根据用例对聚合进行建模,事情会变得更加清晰和容易,因为您只需要跟踪一小部分对象的更改在聚合边界中。
我怀疑客户应该是没有订单列表的单独聚合,而订单应该是带有订单行列表的聚合。如果您需要对客户的每个订单执行操作,请使用 orderRepository.GetOrdersForCustomer(customerID);进行更改然后使用 orderRespository.Save(order);
关于没有 ORM 的更改跟踪,您可以通过多种方式执行此操作,例如订单聚合可以引发订单存储库正在侦听已删除订单行的事件。当工作单元完成时,可以删除这些内容。或者一个稍微不太优雅的方法是维护已删除的列表,即您的存储库显然可以读取的 order.DeletedOrderLines 。
总结一下:
编辑回应评论:
我认为我不会为订单行实现延迟加载。您可能在不需要订单行的情况下对订单执行哪些操作?我怀疑的人并不多。
然而,当 DDD 似乎没有意义时,我不会局限于 DDD 的“规则”,所以...如果在不太可能的情况下,对订单对象执行了许多操作,但这些操作并没有不需要填充订单行并且通常有大量订单行与订单关联(两者都必须对我来说是一个问题),那么我会这样做this:
在订单对象中具有此私有字段:
它将由订单存储库传递到创建时的订单:
其中这是 OrderRepository 上的私有方法:
然后在订单行属性中可能如下所示:
编辑 2
我发现这篇博客文章与我的解决方案类似,但稍微更优雅:
http://thinkbeforecoding.com/post/2009/02/07/Lazy-load-and-persistence-ignorance
Yes, but an ORM simplifies things.
To be honest I think your problem isn't to do with whether you need an ORM or not - it's that you are thinking too much about the data rather than behaviour which is the key for success with DDD. In terms of the data model, most entities will have associations to most another entities in some form, and from this perspective you could traverse all around the model. This is what it looks like with your customer and orders and perhaps why you think you need lazy loading. But you need to use aggregates to break these relationships up into behavioural groups.
For example why have you modelled the customer aggregate to have a list of order? If the answer is "because a customer can have orders" then I'm not sure you're in the mindset of DDD.
What behaviour is there that requires the customer to have a list of orders? When you give more thought to the behaviour of your domain (i.e. what data is required at what point) you can model your aggregates based around use cases and things become much clearer and much easier as you are only change tracking for a small set of objects in the aggregate boundary.
I suspect that Customer should be a separate aggregate without a list of orders, and Order should be an aggregate with a list of order lines. If you need to perform operations on each order for a customer then use orderRepository.GetOrdersForCustomer(customerID); make your changes then use orderRespository.Save(order);
Regarding change tracking without an ORM there are various ways you can do this, for example the order aggregate could raise events that the order repository is listening to for deleted order lines. These could then be deleted when the unit of work completed. Or a slightly less elegant way is to maintain deleted lists, i.e. order.DeletedOrderLines which your repository can obviously read.
To Summarise:
EDIT in response to comment:
I don't think I'd implement lazy loading for order lines. What operations are you likely to perform on the order without needing the order lines? Not many I suspect.
However, I'm not one to be confined to the 'rules' of DDD when it doesn't seem to make sense, so... If in the unlikely scenario that there are a number of operations performed on the order object that didn't require the order lines to be populated AND there are often a large number of order lines associated to an order (both would have to be true for me to consider it an issue) then I'd do this:
Have this private field in the order object:
Which would be passed by the order repository to the order on creation:
Where this is a private method on the OrderRepository:
Then in the order lines property could look like:
Edit 2
I've found this blog post which has a similar solution to mine but slightly more elegant:
http://thinkbeforecoding.com/post/2009/02/07/Lazy-load-and-persistence-ignorance
将订单映射代码与客户映射代码分开可能是个好主意。如果您手动编写数据访问代码,那么从
GetCustomerById
方法调用该映射模块是最佳选择。将所有这些放在一起的逻辑必须存在于某个地方;相关的聚合存储库是一个很好的地方。
我见过的最好的解决方案是让您的存储库返回子类域实体(使用类似 Castle DynamicProxy< /a>) - 让您可以在域模型中保持持久性无知。
It's probably a good idea to separate the order mapping code from the customer mapping code. If you're writing your data access code by hand, calling that mapping module from the
GetCustomerById
method is your best option.The logic to put all those together has to live somewhere; the related aggregate repository is as good a place as any.
The best solution I've seen is to make your repository return subclassed domain entities (using something like Castle DynamicProxy) - that lets you maintain persistence ignorance in your domain model.
另一个可能的答案是创建一个继承自 Customer 的新 Proxy 对象,将其命名为 CustomerProxy,并在那里处理延迟加载。所有这些都是伪代码,因此它是为了给您一个想法,而不仅仅是复制粘贴以供使用。
示例:
这是客户“代理”类...该类并不位于业务层中,而是与上下文和数据映射器一起位于数据层中。请注意,您想要延迟加载的任何集合都应该声明为虚拟(我相信 EF 4.0 还要求您将 props 设为虚拟,就好像在纯 POCO 上运行时旋转代理类一样,以便 Context 可以跟踪更改
) ,在这种情况下,我们的实体对象 Customer 仍然不受数据库/数据映射器代码调用的影响,这就是我们想要的......“纯” POCO 的。您已将延迟加载委托给位于数据层中的代理对象,并实例化数据映射器并进行调用。
这种方法有一个缺点,即调用客户端代码无法覆盖延迟加载......它要么打开,要么关闭。所以这取决于你的具体使用情况。如果您知道可能 75% 的时间您总是需要客户的订单,那么延迟加载可能不是最好的选择。您的 CustomerDataMapper 最好在获取 Customer 实体时填充该集合。
同样,我认为 NHibernate 和 EF 4.0 都允许您在运行时更改延迟加载特性,因此,像往常一样,使用 ORM 是有意义的,因为为您提供了很多功能。
如果您不经常使用 Orders,则可以使用延迟加载来填充 Orders 集合。
我希望这是“正确的”,并且是一种以正确的方式完成域模型设计的延迟加载的方法。我在这方面还是个新手……
迈克
Another possible answer is to create a new Proxy object that inherits from Customer, call it CustomerProxy, and handle the lazy load there. All this is pseudo-code, so it's to give you an idea, not just copy and paste it for use.
Example:
here is the Customer "proxy" class... this class does not live in the business layer, but in the Data Layer along with your Context and Data Mappers. Note that any collections you want to make lazy-load you should declare as virtual (I believe EF 4.0 also requires you to make props virtual, as if spins up proxy classes at runtime on pure POCO's so the Context can keep track of changes)
So, in this case, our entity object, Customer is still free from database/datamapper code calls, which is what we want... "pure" POCO's. You've delegated the lazy-load to the proxy object which lives in the Data layer, and does instantiate data mappers and make calls.
there is one drawback to this approach, which is calling client code can't override the lazy load... it's either on or off. So it's up to you in your particular usage circumstance. If you know maybe 75% of the time you'll always needs the Orders of a Customer, than lazy-load is probably not the best bet. It would be better for your CustomerDataMapper to populate that collection at the time you get a Customer entity.
Again, I think NHibernate and EF 4.0 both allow you to change lazy-loading characteristics at runtime, so, as per usual, it makes sense to use an ORM, b/c a lot of functionality is provided for you.
If you don't use Orders that often, then use a lazy-load to populate the Orders collection.
I hope that this is "right", and is a way of accomplishing lazy-load the correct way for Domain Model designs. I'm still a newbie at this stuff...
Mike