由于所有实体都有谁创建/修改记录的标记,我们是否可以将 Person 实体视为所有实体的聚合根?
也就是说,所有引用Person的实体都将成为Person的集合,例如
public class Person
{
public virtual int PersonId { get; set; }
public virtual string Lastname { get; set; }
public virtual IList<OrderHeader> CreatedOrders { get; set; }
public virtual IList<OrderHeader> ModifiedOrders { get; set; }
// Other entities that have a reference on Person will be mapped as a collection under
// the Person entity
}
public class OrderHeader
{
public virtual int OrderId { get; set; }
public virtual DateTime OrderDate { get; set; }
public virtual Customer Customer { get; set; }
public virtual string CommentsOnThisOrder { get; set; }
// stamp audit-level concerns
public virtual Person CreatedBy { get; set; }
public virtual DateTime DateCreated { get; set; }
public virtual Person ModifiedBy { get; set; }
public virtual DateTime DateModified { get; set; }
public virtual IList<OrderItem> OrderItems { get; set; }
}
public class OrderItem
{
public virtual OrderHeader OrderHeader { get; set; }
public virtual Product Product { get; set; }
public virtual int Quantity { get; set; }
public virtual decimal Price { get; set; }
}
这基本上会使所有实体成为 Person 的集合,这违反了 DDD 聚合根规则。
以我对DDD聚合的有限理解,OrderHeader一定不能成为Person的集合,因为我们不应该通过Person来保存Order聚合。保存订单聚合(对象图)的唯一*入口*点必须从 OrderHeader 完成,而不是从 Person 完成。
现在我的真正目标来了,即使它看起来很脏,我仍然希望 Order 成为 Person 的集合:
如果 OrderHeader 未作为集合映射到 Person,则有一个 ORM *咳咳* NHibernate *咳咳* 无法执行从 Person 到 OrderHeader 的 LEFT JOIN (.DefaultIfEmpty)。实现从 Person 到 OrderHeader 的 LEFT JOIN 的唯一方法是将 OrderHeader 作为集合映射到 Person。
我是否应该允许基础设施问题(例如 facilitating 通过 Linq 的 .DefaultIfEmpty 从 Person 左连接到 OrderHeader,使 OrderHeader 成为 Person 的集合)打破关于何时只有实体必须成为聚合根的规则?
如果我们不将 OrderHeader 作为集合映射到 Person,那么如果我们需要在 NHibernate 上进行从 Person 到 OrderHeader 的 LEFT JOIN(扁平化结果,不分层,因此需要使用 LEFT JOIN),这是唯一的选择左边是使用QueryOver。与 Linq 相比,QueryOver 的编写非常繁琐。
如果我们手动执行从 Person 到 OrderHeader 的 LEFT JOIN(即通过 Linq 的连接和 .DefaultIfEmpty),则无法在 NHibernate 的 Linq 上使用 .DefaultIfEmpty(LEFT JOIN 功能),如果我们执行手动 LEFT JOIN,.DefaultIfEmpty 会抛出异常在 NHibernate 上,NHibernate 的 Linq 上的 LEFT JOIN 必须通过集合和 .DefaultIfEmpty 完成。
如果偶尔这样做(根据需要),打破聚合根的规则是否是一个务实的选择?例如,我是否应该将 OrderHeader 作为集合映射到 Person,以便通过 Linq 实现从 Person 到 OrderHeader 的 LEFT JOIN?
编辑
Northwind 数据库示例。当我们需要报告所有有订单的客户,包括那些没有订单的客户(例如,PARIS)
CustomerID OrderID
OTTIK | 10407
OTTIK | 10684
OTTIK | 10554
PARIS |
PERIC | 10502
PERIC | 10474
PERIC | 10995
PERIC | 10354
PERIC | 11073
PERIC | 10322
,我们需要做一个 LEFT JOIN:
select c.CustomerID, o.OrderID
from customers c
left join orders o on c.CustomerID = o.CustomerID
order by c.CustomerID
这可以通过 Linq 的连接和 .DefaultIfEmpty() 来完成。然而,NHibernate 的 Linq 无法对手动 Linq 连接的结果执行 .DefaultIfEmpty,它会抛出异常。 NHibernate 的 DefaultIfEmpty 只能应用于集合。但我觉得将集合映射到不是聚合根的东西违反了 DDD 聚合根规则。此外,所有实体(例如 Person(Customer 是另一个示例))都可能包含 ALL 实体的集合,因为每个表都有一个 CreatedByPerson 引用。
@丹尼尔席林:
我也很惊讶(以一种好的方式),从 OrderItem 引用 OrderHeader 违反了 DDD。我们是否有某种白皮书或 Martin Fowler 文章对此进行了阐述?我认为从子实体引用父实体不被视为基础设施问题,因此被视为 DDD。
最佳答案
关于 DDD 决策的思考...
听起来你希望能够做到这一点:
var people = session.Query<Person>()
.FetchMany(x => x.CreatedOrders)
.Where(x => x.Id == personId);
你应该像这样翻转查询......
var orders = session.Query<OrderHeader>()
.Fetch(x => x.CreatedBy)
.Where(x => x.CreatedBy.Id == personId);
将 person.CreatedOrders
排除在您的模型之外还有更多原因,而不仅仅是 DDD 纯度。如果你有一个人每天下 10 个订单,一年中的每一天,年复一年怎么办?第一个查询几乎肯定会加载比实际需要更多的数据。第二个查询根本不需要 person.CreatedOrders
集合,它使您可以更好地控制要获取的订单以及要获取的订单数量。
var orders = session.Query<OrderHeader>()
.Fetch(x => x.CreatedBy)
.Where(x => x.CreatedBy.Id == personId)
.Where(x => x.OrderDate >= DateTime.Now.AddYears(-1))
.Skip(100)
.Take(50);
一般来说,如果您发现自己违反了 DDD 原则,这通常是您做错事的一个很好的指标,您通常不必看得太远就可以找到其他支持证据。
不过,每隔一段时间,您就必须稍微改变一下规则,我认为这是可以接受的。例如,我通常尝试只映射每个关系的一侧——从 DDD 的角度来看,这就是我真正需要的。当引用其他聚合根时,这通常意味着只对 many-to-one
端建模,而对于聚合根内的引用 - 从根到它的子节点,这通常意味着只对 建模一对多
并将多对一
留在模型之外。这意味着将 OrderItem.OrderHeader
排除在模型之外。
如果您需要了解已售出多少特定产品,但仅限于已提交的订单,该怎么办?最简单的方法如下:
var quantitySold = session.Query<OrderItem>()
.Where(x => x.Product.Id == productId && x.OrderHeader.Submitted)
.Sum(x => x.Quantity);
因此在这种情况下,我们需要对一对多
orderHeader.OrderItems
和多对一
进行建模> orderItem.OrderHeader
。这是因为此 SUM 最有效的方法是直接访问相关的 OrderItems,而不是像 DDD 要求的那样先通过 OrderHeader
。我同意这一点。
未映射关系上的左外连接
忽略问题的 DDD 部分,可以通过组合两个单独查询的结果来模拟此 LEFT OUTER JOIN - 一个用于获取订单...
var orders = session.Query<OrderHeader>()
.Fetch(x => x.CreatedBy);
...和另一个让没有订单的人:
var peopleWithNoOrders = session.Query<Person>()
.Where(p => !session.Query<OrderHeader>().Any(o => o.CreatedBy == p));
关于c# - 人是聚合根吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18743722/