【问题标题】:EF core 6 selecting null values despite where clause asking for not nullEF 核心 6 选择空值,尽管 where 子句要求不为空
【发布时间】:2021-12-29 05:02:17
【问题描述】:

我有一个这样的 Linq2Sql 查询:

Parent.Include(p => p.Children)
  .Where(p => p.Children.Any(c => c.SomeNullableDateTime == null)
    && p.Children
        .Where(c => c.SomeNullableDateTime == null)
        .OrderBy(c => c.SomeInteger)
        .First()
        .SomeOtherNullableDateTime != null
  )
  .Select(p => p.Children
        .Where(c => c.SomeNullableDateTime == null)
        .OrderBy(c => c.SomeInteger)
        .First()
        .SomeOtherNullableDateTime)
  .ToList();

在从 EF 核心 5 移动到 EF 核心 6 之前,这工作正常。对于 EF 核心 6,结果列表包含一些空值(不应该是这种情况,因为 where 条件要求不为空)。 EF core 6 中是否有一些我不知道的重大更改/限制,或者这只是一个错误?

更新:这是输出的摘录

更新 2:这是生成的 SQL 语句

SELECT(
    SELECT TOP(1)[p1].[SomeOtherNullableDateTime]
    FROM[Children] AS[p1]
    WHERE([p].[Id] = [p1].[ParentId]) AND[p1].[SomeNullableDateTime] IS NULL
    ORDER BY[p1].[SomeInteger])
FROM[Parent] AS[p]
WHERE EXISTS(
    SELECT 1
    FROM[Children] AS [c]
    WHERE ([p].[Id] = [c].[ParentId]) AND[c].[SomeNullableDateTime] IS NULL) AND EXISTS(
   SELECT 1
   FROM[Children] AS [c0]
    WHERE ([p].[Id] = [c0].[ParentId]) AND[c0].[SomeNullableDateTime] IS NULL)
GO

所以看起来问题是 SomeOtherNullableDateTime(应该不为空)甚至没有包含在生成的 SQL 的 where 子句中。

更新 3:这是 SQL EF 核心 5(正确)生成

SELECT (
    SELECT TOP(1) [c].[SomeOtherNullableDateTime]
    FROM [Children] AS [c]
    WHERE ([p].[Id] = [c].[ParentId]) AND [c].[SomeNullableDateTime] IS NULL
    ORDER BY [c].[SomeInteger])
FROM [Parent] AS [p]
WHERE EXISTS (
    SELECT 1
    FROM [Children] AS [c0]
    WHERE ([p].[Id] = [c0].[ParentId]) AND [c0].[SomeNullableDateTime] IS NULL) AND (
    SELECT TOP(1) [c1].[SomeOtherNullableDateTime]
    FROM [Children] AS [c1]
    WHERE ([p].[Id] = [c1].[ParentId]) AND [c1].[SomeNullableDateTime] IS NULL
    ORDER BY [c1].[SomeInteger]) IS NOT NULL
GO

【问题讨论】:

  • 生成的SQL语句是什么?
  • @StefanGolubović:我已将生成的 sql 语句添加到问题中
  • 我觉得奇怪的是你没有在声明的末尾加上 .First。
  • 为什么是Include,然后是Select?这两件事不能很好地结合在一起(很可能Include 被忽略了)。我只想Select + Where
  • @IvanStoev:我只是想弄清楚你想说什么:-D

标签: entity-framework-core ef-core-6.0


【解决方案1】:

看起来像 EF Core 6.0 查询翻译错误。如果您使用“更自然”的方式编写此类查询,也会发生同样的情况

var query = db.Set<Parent>()
    .Select(p => p.Children
        .Where(c => c.SomeNullableDateTime == null)
        .OrderBy(c => c.SomeInteger)
        .FirstOrDefault())
    .Where(c => c.SomeOtherNullableDateTime != null)
    .Select(c => c.SomeOtherNullableDateTime);

生成的 SQL

SELECT (
    SELECT TOP(1) [c0].[SomeOtherNullableDateTime]
    FROM [Child] AS [c0]
    WHERE ([p].[Id] = [c0].[ParentId]) AND [c0].[SomeNullableDateTime] IS NULL
    ORDER BY [c0].[SomeInteger])
FROM [Parent] AS [p]
WHERE EXISTS (
    SELECT 1
    FROM [Child] AS [c]
    WHERE ([p].[Id] = [c].[ParentId]) AND [c].[SomeNullableDateTime] IS NULL)

也缺少IS NOT NULL 条件,因此您可能会在错误报告中包含这种情况。

等效模式(使用SelectMany + Take(1) 而不是Select + FirstOrDefault()

var query = db.Set<Parent>()
    .SelectMany(p => p.Children
        .Where(c => c.SomeNullableDateTime == null)
        .OrderBy(c => c.SomeInteger)
        .Take(1))
    .Where(c => c.SomeOtherNullableDateTime != null)
    .Select(c => c.SomeOtherNullableDateTime);

(与@Svyatoslav 同时建议的相同),生成不同的 SQL

SELECT [t0].[SomeOtherNullableDateTime]
FROM [Parent] AS [p]
INNER JOIN (
    SELECT [t].[ParentId], [t].[SomeNullableDateTime], [t].[SomeOtherNullableDateTime]
    FROM (
        SELECT [c].[ParentId], [c].[SomeNullableDateTime], [c].[SomeOtherNullableDateTime], ROW_NUMBER() OVER(PARTITION BY [c].[ParentId], [c].[SomeNullableDateTime] ORDER BY [c].[SomeInteger]) AS [row]
        FROM [Child] AS [c]
    ) AS [t]
    WHERE [t].[row] <= 1
) AS [t0] ON ([p].[Id] = [t0].[ParentId]) AND [t0].[SomeNullableDateTime] IS NULL
WHERE [t0].[SomeOtherNullableDateTime] IS NOT NULL

它有IS NOT NULL 条件,但现在内部子查询看起来是错误的,因为它选择了按某些东西排序的每个第一个子查询,然后应用IS NULL 条件,而LINQ 查询请求首先应用IS NULL 条件,然后选择由某物订购的第一个孩子。所以你也可以在错误报告中包含这个用例。

所有这些查询,包括来自 OP 的查询,在 EF Core 5.0 中都能正常工作(生成正确的 SQL)。

【讨论】:

  • 是的,子查询优化中的严重错误。他们不应该改变窗口搜索条件。
  • 所以我们在一个 SO 问题中发现了两个不同的问题。可能您必须为他们创建新问题。
  • @Ivan Stoev:我已将您的两个示例添加到 GitHub Ticket (github.com/dotnet/efcore/issues/26744)
  • @Ivan Stoev:开发团队已经确认了与这些查询相关的两个错误。不幸的是,它们最早要到明年二月才能修复。因此,我写了另一个答案作为对其他开发人员的警告。
  • @Ivan Stoev:我在 GitHub 上询问过这些错误是否仅限于所提供的场景,或者是否还有其他类型的查询受到影响。如果我得到答案,我会将其包含在我的答案中。
【解决方案2】:

GitHub 上的开发团队已确认有两个不同的错误会导致这些问题:

https://github.com/dotnet/efcore/issues/26744

https://github.com/dotnet/efcore/issues/26756

不幸的是,他们表示这些错误不会在计划于 12 月发布的 6.0.1 版本中修复,但最早会在计划于 2022 年 2 月发布的另一个版本中修复。

由于这些错误导致 EF Core 6 悄悄地返回错误的结果,并且很可能许多用户会弄乱他们的数据或根据错误的数据做出决定(因为没有人会检查所有 Linq2SQL 查询以确保 SQL 生成正确!? ) 我建议暂时不要使用 EF core 6!

这可能被视为基于意见,但请不要删除此答案,而是将其作为对开发者的警告!

更新: 现在有针对这些问题的修复:

https://github.com/dotnet/efcore/pull/27284

https://github.com/dotnet/efcore/pull/27292

它们已获准与计划于 2022 年 3 月发布的 6.0.3 版本一起发布。

【讨论】:

    【解决方案3】:

    虽然它可能是回归,但我建议以有效且更可预测的方式重写查询:

    var query =
        from p in Parent
        from c in p.Children
            .Where(c.SomeNullableDateTime == null)
            .OrderBy(c => c.SomeInteger)
            .Take(1)
        where c.SomeOtherNullableDateTime != null
        select c.SomeOtherNullableDateTime;
    

    【讨论】:

    • 不幸的是,这不起作用(请参阅我的回答),因此该错误似乎比看起来更严重。我猜是一些混乱的优化。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-08-13
    • 1970-01-01
    • 1970-01-01
    • 2016-04-17
    • 1970-01-01
    • 2012-03-13
    • 1970-01-01
    相关资源
    最近更新 更多