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 导航 属性 表示 joinleftinner 取决于它是否是 required* or *optional*) and *collection* navigation property represents agroup 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 查询问题。