【问题标题】:EFCore returning too many columns for a simple LEFT OUTER joinEFCore 为简单的 LEFT OUTER 联接返回太多列
【发布时间】:2016-11-11 09:15:31
【问题描述】:

我目前正在使用带有 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 JOINS 根本不起作用的问题)。但是 SQL 很糟糕,并且包含 OrderOrderItem 中的每一列,考虑到其中一个是大型 XML Blob,这是有问题的。

SELECT [order].[OrderId], [order].[OrderStatusTypeId], [order].[OrderSummary], [order].[OrderTotal], [order].[OrderTypeId], [order].[ParentFSPID], [order].[ParentOrderId], [订单].[PayPalECToken],[订单].[PaymentFailureTypeId] ....

...[orderItem].[OrderId], [orderItem].[OrderItemType], [orderItem].[Qty], [orderItem].[SKU] FROM [Order] AS [order] LEFT JOIN [OrderItem] AS [orderItem] ON [order].[OrderId] = [orderItem].[OrderId] ORDER BY [订单].[订单 ID]

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

另一方面 - 如果我将其设为 INNER JOIN,则 SQL 与预期的一样,只有我的 select 子句中的列:

SELECT [order].[OrderDt], [orderItem].[SKU], [orderItem].[Qty] FROM [Order] AS [order] INNER JOIN [OrderItem] AS [orderItem] ON [order].[OrderId] = [orderItem].[OrderId]

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

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


编辑:EFCore 2.1 解决了很多分组问题以及 N+1 类型问题,即为每个子实体进行单独查询。事实上,表演给我留下了深刻的印象。

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

【问题讨论】:

  • 希望这会在 1.1 最终版本中得到修复,但不幸的是它没有:-(
  • @IvanStoev 真希望他们能更清楚地说明它本质上是未完成的软件。他们称之为 1.1,但实际上是 0.9。期待 1.2(或者我喜欢称之为 1.0):-)
  • 更新:在 github 上看到了一些关于 GroupBy 下一个版本的进展的有希望的注释 :-) 包括一个承诺,它修复了许多这样的问题 :-)
  • 我完全同意你的看法。我以前从未见过 MS 甚至测试版发布过这样的东西。看起来开源正在开启新的(坏的)实践。但我确信 EF Core 开发人员知道这一点。管理层很可能正在推动(因为 ASP.NET Core 堆栈),当然营销永远不会允许将其称为“未完成的软件”。是的,文档的Compare EF Core & EF6.x 部分有几句话,特别是Which One Is Right for You...
  • ...部分,仅此而已,并且没有为开始使用 EF Core 时遇到的所有问题和错误提供足够的详细信息。根据我的经验,目前entity-framework-core SO 标签中 95% 以上的帖子都是由软件中的错误引起的。

标签: entity-framework entity-framework-core


【解决方案1】:

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

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

我可以根据测试说的是,问题很可能与GroupJoin 翻译中的错误(回归、不完整的实现 - 真的很重要)有关。例如,#7003: Wrong SQL generated for query with group join on a subquery that is not present in the final projection#6647 - Left Join (GroupJoin) always materializes elements resulting in unnecessary data pulling 等。

直到它得到修复(何时?),作为一种(远非完美的)解决方法,我可以建议使用替代的左外连接语法 (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(又一个错误)。

【讨论】:

  • 非常感谢您运行这些测试。你知道这是否已被报告或者我是否需要提交错误
  • 我建议举报。我目前在GroupJoin 区域看到了大约 10 个未解决的问题,其中一些是最近出现的,但不完全是这个(尽管我可能会错过它或者可能会以不同的方式报告)。即使它是重复的,我想报告它也不会受到伤害:)
  • @Simon_Weaver 你最后举报了吗?
  • 是的。我认为这些问题中的大多数现在都已得到解决。我一直在使用预发布版本
【解决方案2】:

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

参考: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
               };

【讨论】:

  • 这是一个内部连接。我需要一个外部连接。也许有一种方法可以重写它,以便“into”只包含正确的列,但这不是:-/
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-02
  • 1970-01-01
  • 2016-05-07
  • 1970-01-01
  • 2010-10-29
  • 2013-03-19
相关资源
最近更新 更多