对于一个简单的LEFT OUTER连接,EFCore返回了太多的列



我目前正在使用SQL Server的EFCore 1.1(预览版)。

我正在做我认为是OrderOrderItem表之间的一个简单的OUTER JOIN。

      var orders = from order in ctx.Order
                   join orderItem in ctx.OrderItem
                   on order.OrderId equals orderItem.OrderId into tmp
                   from oi in tmp.DefaultIfEmpty()
                   select new
                   {
                       order.OrderDt,
                       Sku = (oi == null) ? null : oi.Sku,
                       Qty = (oi == null) ? (int?) null : oi.Qty
                   };

返回的实际数据是正确的(我知道早期版本存在OUTER join根本无法工作的问题)。然而,SQL是可怕的,包括OrderOrderItem的每一列,考虑到其中一个是一个大的XML Blob,这是有问题的。

选择(秩序)。(OrderId),(顺序)。[OrderStatusTypeId],(订单)。[OrderSummary],(顺序)。[OrderTotal],[顺序]。[OrderTypeId],(订单)。[ParentFSPId],[顺序]。[ParentOrderId],(订单)。[PayPalECToken],(顺序)。[PaymentFailureTypeId]…

…[orderItem]。[OrderId], [orderItem]。[OrderItemType], [orderItem]。(数量),[orderItem]。[SKU]来自[Order] AS [Order]左连接[OrderItem] AS[orderItem] ON [order]。[OrderId] = [orderItem]。[OrderId]排序(顺序)。(OrderId)

(这里没有显示更多的列)

另一方面,如果我使它成为一个INNER JOIN,那么SQL就像预期的那样只有我的select子句中的列:

选择(秩序)。[OrderDt], [orderItem]。[SKU], [orderItem]。(数量)从[Order]作为[Order]内部连接[OrderItem]作为[OrderItem] ON(订单)。[OrderId] = [orderItem].[OrderId]

我尝试恢复到EFCore 1.01,但得到一些可怕的nuget包错误,放弃了。

不清楚这是实际的回归问题还是EFCore中不完整的功能。但是在其他地方找不到更多的信息。


编辑:EFCore 2.1已经解决了很多问题与分组和N+1类型的问题,其中一个单独的查询是为每个子实体。事实上,我对演出印象非常深刻。

3/14/18 -不推荐EFCore的2.1预览1,因为在使用OrderBy()时,GROUP BY SQL有一些问题,但在夜间构建和预览2中已经修复。

以下内容适用于EF Core 1.1.0(发行版)。

虽然不应该做这样的事情,尝试了几种替代语法查询(使用导航属性代替手动连接,连接包含匿名类型投影的子查询,使用let/中间Select,使用Concat/Union来模拟左连接,替代左连接语法等)的结果-无论是相同的帖子,和/或执行多个查询,和/或无效的SQL查询,和/或奇怪的运行时异常,如IndexOutOfRange, InvalidArgument等。

根据测试,我能说的是,最有可能的问题是与GroupJoin翻译中的bug(回归,不完整的实现-这真的重要吗)有关。例如,#7003:在最终投影中不存在的子查询上使用组连接生成错误的SQL; #6647 -左连接(GroupJoin)总是物化元素,导致不必要的数据拉出等。

直到它得到修复(何时?),作为一个(远非完美的)解决方案,我可以建议使用替代的左外连接语法(from a in A from b in B.Where(b = b.Key == a.Key).DefaultIfEmpty()):

var orders = from o in ctx.Order
             from oi in ctx.OrderItem.Where(oi => oi.OrderId == o.OrderId).DefaultIfEmpty()
             select new
             {
                 OrderDt = o.OrderDt,
                 Sku = oi.Sku,
                 Qty = (int?)oi.Qty
             };

生成如下SQL:

SELECT [o].[OrderDt], [t1].[Sku], [t1].[Qty]
FROM [Order] AS [o]
CROSS APPLY (
    SELECT [t0].*
    FROM (
        SELECT NULL AS [empty]
    ) AS [empty0]
    LEFT JOIN (
        SELECT [oi0].*
        FROM [OrderItem] AS [oi0]
        WHERE [oi0].[OrderId] = [o].[OrderId]
    ) AS [t0] ON 1 = 1
) AS [t1]

正如您所看到的,投影是可以的,但是它使用了奇怪的CROSS APPLY而不是LEFT JOIN,这可能会引入另一个性能问题。

还请注意,在如上所示访问右连接表时,必须对值类型使用强制类型转换,而对字符串不使用强制类型转换。如果在原始查询中使用null检查,则在运行时将得到ArgumentNullException(这又是一个错误)。

使用"into"将创建一个临时标识符来存储结果。

Reference: MDSN: into (c# Reference)

因此,删除" into tmp from oi in tmp.DefaultIfEmpty() "将导致具有三列的干净sql。

var orders = from order in ctx.Order
               join orderItem in ctx.OrderItem
               on order.OrderId equals orderItem.OrderId
               select new
               {
                   order.OrderDt,
                   Sku = (oi == null) ? null : oi.Sku,
                   Qty = (oi == null) ? (int?) null : oi.Qty
               };

最新更新