使用 EF Core 对聚合根建模 3.X
Modelling Aggregate Roots with EF Core 3.X
假设我有这些实体:
- 客户
- 订单
- 产品
三个都是聚合根,因为三个都需要独立加载和修改。由于聚合根不能包含其他聚合根,对于 Order 聚合根,我们还需要几个实体:OrderCustomer 和 OrderProduct。它们是只读的,并且是 Order 聚合的子项。
这样,由于 Order 聚合需要客户和产品,它会加载只读的 OrderCustomer 和 OrderProduct 实体。订单从不加载客户或产品。
然而,这一切都是有道理的,我的问题是,在无法将多个模型映射到相同 table 的 EF Core 中,这是如何实现的?您是否将 OrderCustomer 和 OrderProduct 定义为非映射模型并手动将它们加载到您的订单存储库中?我看不到任何其他方法可以做到这一点。
(请不要建议使用存储过程或直接 SQL 或类似的东西)
OrderProduct
和 OrderCustomer
可能不是实体,而是值对象,它们的数据与 Order
一起保存。从现有 Product
和 Customer
表中获取数据是不必要的,在某些情况下,这对您的数据完整性非常不利。
由于一个聚合不应该包含对另一个聚合的引用,正如您所想的那样,这个想法是通过 id 引用相关的聚合。但是,如果您需要的信息不仅仅是 id,您可以再次沿着您似乎在想的方向创建一个包含 id 以及一些相关位的值对象。我喜欢将特定于聚合的值对象嵌套 类 以使其所属的位置显而易见,但这是一种设计选择。
As far as "duplicating* the data of the related aggregate in the Order
to re-constitute the value object consider a Product
that represents a pair of "red socks”。如果您只存储 ID,然后使用产品 ID 获取描述,同时描述可能已更改为 "blue socks"。这将导致您的客户出现一些问题。将产品描述非规范化为 Order
(通过 OrderLine
或 Order.Line
)不会在这方面造成任何混淆,并将差异保留在内部自从订购了一对 "red socks" 后,公司没有影响客户。
使用 EF Core 时,如果 OrderCustomer 和 OrderProduct 是属于您的订单聚合根的实体,您可以像您所说的那样在存储库中手动加载它们。这种方法没有错。事实上,微软自己的体系结构指南就是这样做的。这是示例中的代码:
public async Task<Order> GetAsync(int orderId)
{
var order = await _context.Orders.FindAsync(orderId);
if (order != null)
{
await _context.Entry(order)
.Collection(i => i.OrderItems).LoadAsync();
await _context.Entry(order)
.Reference(i => i.OrderStatus).LoadAsync();
await _context.Entry(order)
.Reference(i => i.Address).LoadAsync();
}
return order;
}
假设我有这些实体:
- 客户
- 订单
- 产品
三个都是聚合根,因为三个都需要独立加载和修改。由于聚合根不能包含其他聚合根,对于 Order 聚合根,我们还需要几个实体:OrderCustomer 和 OrderProduct。它们是只读的,并且是 Order 聚合的子项。 这样,由于 Order 聚合需要客户和产品,它会加载只读的 OrderCustomer 和 OrderProduct 实体。订单从不加载客户或产品。
然而,这一切都是有道理的,我的问题是,在无法将多个模型映射到相同 table 的 EF Core 中,这是如何实现的?您是否将 OrderCustomer 和 OrderProduct 定义为非映射模型并手动将它们加载到您的订单存储库中?我看不到任何其他方法可以做到这一点。
(请不要建议使用存储过程或直接 SQL 或类似的东西)
OrderProduct
和 OrderCustomer
可能不是实体,而是值对象,它们的数据与 Order
一起保存。从现有 Product
和 Customer
表中获取数据是不必要的,在某些情况下,这对您的数据完整性非常不利。
由于一个聚合不应该包含对另一个聚合的引用,正如您所想的那样,这个想法是通过 id 引用相关的聚合。但是,如果您需要的信息不仅仅是 id,您可以再次沿着您似乎在想的方向创建一个包含 id 以及一些相关位的值对象。我喜欢将特定于聚合的值对象嵌套 类 以使其所属的位置显而易见,但这是一种设计选择。
As far as "duplicating* the data of the related aggregate in the Order
to re-constitute the value object consider a Product
that represents a pair of "red socks”。如果您只存储 ID,然后使用产品 ID 获取描述,同时描述可能已更改为 "blue socks"。这将导致您的客户出现一些问题。将产品描述非规范化为 Order
(通过 OrderLine
或 Order.Line
)不会在这方面造成任何混淆,并将差异保留在内部自从订购了一对 "red socks" 后,公司没有影响客户。
使用 EF Core 时,如果 OrderCustomer 和 OrderProduct 是属于您的订单聚合根的实体,您可以像您所说的那样在存储库中手动加载它们。这种方法没有错。事实上,微软自己的体系结构指南就是这样做的。这是示例中的代码:
public async Task<Order> GetAsync(int orderId)
{
var order = await _context.Orders.FindAsync(orderId);
if (order != null)
{
await _context.Entry(order)
.Collection(i => i.OrderItems).LoadAsync();
await _context.Entry(order)
.Reference(i => i.OrderStatus).LoadAsync();
await _context.Entry(order)
.Reference(i => i.Address).LoadAsync();
}
return order;
}