GroupJoin始终在本地评估

时间:2019-07-19 19:54:46

标签: c# .net linq .net-core linq-to-entities

我有一个使用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(),因此它将在本地进行评估,从而导致对数据库的两次单独调用。

查询中可以进行哪些更改以使其在服务器上进行评估?

1 个答案:

答案 0 :(得分:1)

此查询存在多个问题。

首先,它使用投影,因此Include / ThenInclude是多余的,它们是ignored,通常记录为警告,但也可以配置为引发异常。

第二,这是主要问题,是将手动联接和导航属性混合在一起。根据包含的内容,您具有适当的导航属性,因此您根本不要使用手动联接- reference 导航属性表示joinleft或{{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查询问题。