GroupJoin 总是在本地评估
GroupJoin always evaluating locally
我有一个使用 GroupJoin 的 linq 查询,并且总是在本地评估导致命中 SQL 服务器 n 次,其中 n 是订单数。
我有 3 个表 Orders、OrderStatus、OrderStatusCode。
Orders has OrderId, CustomerId, ProductId
OrderStatus has OrderId, OrderStatusId, OrderStatusCodeId,
OrderStatusCode has OrderStatusCodeId, OrderStatusCodeName
OrderId, CustomerId, ProductId
1 5000 100
2 5400 100
OrderId, OrderStatusId, OrderStatusCodeId CreatedDateTime
1 1 1 -- started 12/01/2019
1 2 2 -- completed 12/01/2019
1 3 3 -- shipped 12/03/2019
2 1 1 -- started 12/01/2019
2 2 4 -- canceled 12/01/2019
2 3 5 -- refunded 12/10/2019
OrderStatusCodeId, OrderStatusCodeName
1 started
2 completed
3 shipped
4 canceled
5 refunded
var OrderWithLatestStatus = _dbContext.Orders.Include(h =>
h.OrderStatus).ThenInclude(hs => hs.OrderStatusCode)
.Where(o => o.ProductId == "100")
.GroupJoin(_dbContext.OrderStatus,
order => order.OrderId,
status => status.OrderId,
(o, g) => new
{
Order = o,
OrderStatuses = g
})
.Select(x => new EvalWithStatus
{
OrderId = x.Order.OrderId,
CustomerId = x.Order.CustomerId,
AllStatuses = x.OrderStatuses,
LatestOrderStatusCodeName = x.OrderStatuses.Any() ?
x.OrderStatuses.Any(s =>
s.OrderStatusCode.OrderStatusCodeName.Equals("Canceled"))
? x.OrderStatuses.FirstOrDefault(s =>
s.OrderStatusCode.OrderStatusCodeName.Equals("Canceled"))
.OrderStatusCode.OrderStatusCodeName :
x.OrderStatuses.OrderByDescending(s =>
s.CreatedDateTime).FirstOrDefault()
.OrderStatusCode.OrderStatusCodeName
: "Unknown"
}
内部 select 我希望最新状态显示已取消的订单和已完成项目的实际最新状态
OrderId, CustomerId, AllStatuses, LatestOrderStatusCodeName
1 5000 IEnuerable<OrderStatus> Shipped
2 5400 IEnuerable<OrderStatus> Canceled
linq 查询表示它无法评估 x.OrderStatuses.Any() 因此它将在本地评估,从而导致对数据库的两次单独调用。
查询中可以更改哪些内容以使其在服务器上进行计算?
这个查询有几个问题。
首先,它使用投影,所以 Include
/ ThenInclude
是多余的,它们是 ignored 并且通常记录为警告,但也可以配置为抛出异常。
其次,这是混合手动连接和导航属性的主要问题。根据包含,您具有适当的导航属性,因此您根本不应该使用手动连接 - reference 导航 属性 表示 join
(left
或 inner
取决于它是否是 required* or *optional*) and *collection* navigation property represents a
group join`。
在你的情况下,Order.OrderStatus
属性(它应该被称为 OrderStatuses
)正好代表你的 GroupJoin
.[=30 中的 OrderStatuses
=]
所以只需将 GroupJoin
替换为
.Select(o => new { Order = o, OrderStatuses = o.OrderStatus })
或者在最终投影中直接使用,解决客户评价
但是,您可以做得更好。所有这些 Any
/ FirstOrDefault
即使有服务器评估也会导致对相关表的几个 SQL 子查询。可以使用适当的顺序将它们缩减为单个 TOP 1
子查询,例如
var OrderWithLatestStatus = _dbContext.Orders
.Select(order => new EvalWithStatus
{
OrderId = order.OrderId,
CustomerId = order.CustomerId,
AllStatuses = order.OrderStatus.ToList(),
LatestOrderStatus = order.OrderStatus
.OrderBy(s => s.OrderStatusCode.OrderStatusCodeName == "canceled" ? 0 : 1)
.ThenByDescending(s => s.CreatedDateTime)
.Select(s => s.OrderStatusCode.OrderStatusCodeName)
.FirstOrDefault() ?? "Unknown"
});
另请注意ToList()
在此调用
AllStatuses = order.OrderStatus.ToList(),
这是针对 EF Core 2.1 opt-in Optimization of correlated subqueries 并消除了此类数据的 N + 1 查询问题。
我有一个使用 GroupJoin 的 linq 查询,并且总是在本地评估导致命中 SQL 服务器 n 次,其中 n 是订单数。
我有 3 个表 Orders、OrderStatus、OrderStatusCode。
Orders has OrderId, CustomerId, ProductId
OrderStatus has OrderId, OrderStatusId, OrderStatusCodeId,
OrderStatusCode has OrderStatusCodeId, OrderStatusCodeName
OrderId, CustomerId, ProductId
1 5000 100
2 5400 100
OrderId, OrderStatusId, OrderStatusCodeId CreatedDateTime
1 1 1 -- started 12/01/2019
1 2 2 -- completed 12/01/2019
1 3 3 -- shipped 12/03/2019
2 1 1 -- started 12/01/2019
2 2 4 -- canceled 12/01/2019
2 3 5 -- refunded 12/10/2019
OrderStatusCodeId, OrderStatusCodeName
1 started
2 completed
3 shipped
4 canceled
5 refunded
var OrderWithLatestStatus = _dbContext.Orders.Include(h =>
h.OrderStatus).ThenInclude(hs => hs.OrderStatusCode)
.Where(o => o.ProductId == "100")
.GroupJoin(_dbContext.OrderStatus,
order => order.OrderId,
status => status.OrderId,
(o, g) => new
{
Order = o,
OrderStatuses = g
})
.Select(x => new EvalWithStatus
{
OrderId = x.Order.OrderId,
CustomerId = x.Order.CustomerId,
AllStatuses = x.OrderStatuses,
LatestOrderStatusCodeName = x.OrderStatuses.Any() ?
x.OrderStatuses.Any(s =>
s.OrderStatusCode.OrderStatusCodeName.Equals("Canceled"))
? x.OrderStatuses.FirstOrDefault(s =>
s.OrderStatusCode.OrderStatusCodeName.Equals("Canceled"))
.OrderStatusCode.OrderStatusCodeName :
x.OrderStatuses.OrderByDescending(s =>
s.CreatedDateTime).FirstOrDefault()
.OrderStatusCode.OrderStatusCodeName
: "Unknown"
}
内部 select 我希望最新状态显示已取消的订单和已完成项目的实际最新状态
OrderId, CustomerId, AllStatuses, LatestOrderStatusCodeName
1 5000 IEnuerable<OrderStatus> Shipped
2 5400 IEnuerable<OrderStatus> Canceled
linq 查询表示它无法评估 x.OrderStatuses.Any() 因此它将在本地评估,从而导致对数据库的两次单独调用。
查询中可以更改哪些内容以使其在服务器上进行计算?
这个查询有几个问题。
首先,它使用投影,所以 Include
/ ThenInclude
是多余的,它们是 ignored 并且通常记录为警告,但也可以配置为抛出异常。
其次,这是混合手动连接和导航属性的主要问题。根据包含,您具有适当的导航属性,因此您根本不应该使用手动连接 - reference 导航 属性 表示 join
(left
或 inner
取决于它是否是 required* or *optional*) and *collection* navigation property represents a
group join`。
在你的情况下,Order.OrderStatus
属性(它应该被称为 OrderStatuses
)正好代表你的 GroupJoin
.[=30 中的 OrderStatuses
=]
所以只需将 GroupJoin
替换为
.Select(o => new { Order = o, OrderStatuses = o.OrderStatus })
或者在最终投影中直接使用,解决客户评价
但是,您可以做得更好。所有这些 Any
/ FirstOrDefault
即使有服务器评估也会导致对相关表的几个 SQL 子查询。可以使用适当的顺序将它们缩减为单个 TOP 1
子查询,例如
var OrderWithLatestStatus = _dbContext.Orders
.Select(order => new EvalWithStatus
{
OrderId = order.OrderId,
CustomerId = order.CustomerId,
AllStatuses = order.OrderStatus.ToList(),
LatestOrderStatus = order.OrderStatus
.OrderBy(s => s.OrderStatusCode.OrderStatusCodeName == "canceled" ? 0 : 1)
.ThenByDescending(s => s.CreatedDateTime)
.Select(s => s.OrderStatusCode.OrderStatusCodeName)
.FirstOrDefault() ?? "Unknown"
});
另请注意ToList()
在此调用
AllStatuses = order.OrderStatus.ToList(),
这是针对 EF Core 2.1 opt-in Optimization of correlated subqueries 并消除了此类数据的 N + 1 查询问题。