2

我有一个这样的 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 核心 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
4

3 回答 3

2

看起来像 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)。

于 2021-11-18T11:59:13.000 回答
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 版本一起发布。

于 2021-11-19T10:40:12.547 回答
0

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

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;
于 2021-11-18T11:41:51.720 回答