我有一个使用GroupJoin的linq查询,并且总是在本地进行评估,导致n次命中SQL服务器,其中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"
}
在内部选择中,我希望已取消订单的最新状态显示为已取消,而已完成商品的实际最新状态为
OrderId, CustomerId, AllStatuses, LatestOrderStatusCodeName
1 5000 IEnuerable<OrderStatus> Shipped
2 5400 IEnuerable<OrderStatus> Canceled
linq查询说它无法评估x.OrderStatuses.Any(),因此它将在本地进行评估,从而导致对数据库的两次单独调用。
查询中可以进行哪些更改以使其在服务器上进行评估?
答案 0 :(得分:1)
此查询存在多个问题。
首先,它使用投影,因此Include
/ ThenInclude
是多余的,它们是ignored,通常记录为警告,但也可以配置为引发异常。
第二,这是主要问题,是将手动联接和导航属性混合在一起。根据包含的内容,您具有适当的导航属性,因此您根本不要使用手动联接- reference 导航属性表示join
(left
或{{1} },具体取决于它是否是inner
组联接`。
在您的情况下,required* or *optional*) and *collection* navigation property represents a
属性(应称为Order.OrderStatus
)恰好代表了OrderStatuses
中的OrderStatuses
。
因此只需将GroupJoin
替换为
GroupJoin
或直接在最终投影中使用将解决客户评估问题。
但是,您可以做得更好。所有这些.Select(o => new { Order = o, OrderStatuses = o.OrderStatus })
/ Any
甚至带有服务器评估都会导致对相关表的多个SQL子查询。可以使用适当的顺序将它们修剪成单个FirstOrDefault
子查询,例如
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()
这是为了加入EF Core 2.1 Optimization of correlated subqueries,并消除了此类数据的N + 1查询问题。