我在不使用ORM的.NET C#应用程序中使用存储库模式。但是,我遇到的问题是如何填充实体的一对多列表属性。例如如果客户有一个订单列表,即如果Customer类有一个名为Orders的List属性,而我的存储库有一个名为GetCustomerById的方法,那么呢?
这也引发了对变更跟踪,删除等功能的质疑?所以我认为最终结果是我可以在没有ORM的情况下进行DDD吗?
但是现在我只对我的域实体中的延迟加载List属性感兴趣?有什么想法吗?
纳比尔
我认为对于没有在域驱动设计中使用ORM的人来说,这是一个非常常见的问题?有什么想法吗?
答案 0 :(得分:12)
我可以在没有ORM的情况下进行DDD吗?
是的,但ORM简化了事情。
老实说,我认为你的问题与你是否需要ORM无关 - 这是因为你过多地考虑数据而不是行为,这是DDD成功的关键。就数据模型而言,大多数实体将以某种形式与大多数其他实体建立关联,从这个角度来看,您可以遍历整个模型。这就是您的客户和订单的样子,也许您认为需要延迟加载的原因。但是你需要使用聚合来将这些关系分解为行为组。
例如,为什么您将客户聚合建模为具有订单列表?如果答案是“因为客户可以接受订单”,那么我不确定您是否处于DDD的心态。
哪些行为需要客户拥有订单列表?当您更多地考虑域的行为(即在什么点需要什么数据)时,您可以根据用例对事物进行建模,事情变得更加清晰和简单,因为您只需更改跟踪一小组对象在总边界。
我怀疑客户应该是一个没有订单列表的单独聚合,订单应该是一个包含订单行列表的聚合。如果您需要对客户的每个订单执行操作,请使用orderRepository.GetOrdersForCustomer(customerID);进行更改然后使用orderRespository.Save(order);
关于没有ORM的更改跟踪,您可以通过多种方式执行此操作,例如,订单聚合可能会引发订单存储库正在侦听已删除订单行的事件。然后可以在完成工作单元时删除这些内容。或者稍微不那么优雅的方法是维护已删除的列表,即您的存储库显然可以读取的order.DeletedOrderLines。
总结:
编辑回复评论:
我认为我不会为订单行实施延迟加载。您可以在订单上执行哪些操作而无需订单行?我怀疑的并不多。
然而,当它看起来没有意义时,我不是一个被限制在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集合。
我希望这是“正确的”,并且是一种为域模型设计实现延迟加载正确方法的方法。我还是这个东西的新手...
麦克