c# - 人是聚合根吗?

标签 c# linq nhibernate domain-driven-design aggregate

由于所有实体都有谁创建/修改记录的标记,我们是否可以将 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/

相关文章:

c# - ‘finally’ block 可以更改其 ‘try’ block 的返回值吗?

c# - 如何使用C#检查Microsoft Outlook是否已打开?

c# - TFS - 链式构建 - 如何将信息从一个构建传递到下一个构建?

c# - ShellFile.FromFilePath 中的奇怪错误

.net - 如何使用 dbml 文件连接 oracle 数据库?

c# - Linq 按条件排序

c# - 我的 Saga 结构是正确的解决方案吗? (N服务总线)

C# - MongoDB Linq 查询因嵌入式文档界面而失败

NHibernate vs Entity Framework 5 自动连接实体

c# - Asp.net MVC 休眠 : Can not modify more than one base table through a join view