Linq 到 sql 实体排序

vic*_*tor 5 c# linq entity-framework

我正在使用EF和进行一些测试Linq to entities,以尝试提高我的应用程序性能。

我只是注意到一些奇怪的事情(对我来说),我无法解释,也无法真正判断是否会产生相当大的开销。

这是我的链接:

var result = from n in query
        orderby n.PersonId
        select new
        {
            id = n.Id,
            appointmentId = n.AppointmentId,
            message = n.Message,
            wasRead = n.Read,
            canDismiss = (n.Appointment.Status != AppointmentStatus.Waiting),
            date = n.IssueDateUtc
        };
Run Code Online (Sandbox Code Playgroud)

这是生成的sql:

SELECT 
    [Project1].[Id] AS [Id], 
    [Project1].[AppointmentId] AS [AppointmentId], 
    [Project1].[Message] AS [Message], 
    [Project1].[Read] AS [Read], 
    [Project1].[C1] AS [C1], 
    [Project1].[IssueDateUtc] AS [IssueDateUtc]
    FROM ( SELECT 
        [Extent1].[Id] AS [Id], 
        [Extent1].[Read] AS [Read], 
        [Extent1].[Message] AS [Message], 
        [Extent1].[IssueDateUtc] AS [IssueDateUtc], 
        [Extent1].[AppointmentId] AS [AppointmentId], 
        [Extent1].[PersonId] AS [PersonId], 
        CASE WHEN ( NOT ((1 = [Extent2].[Status]) AND ([Extent2].[Status] IS NOT NULL))) THEN cast(1 as bit) WHEN (1 = [Extent2].[Status]) THEN cast(0 as bit) END AS [C1]
        FROM  [dbo].[Notification] AS [Extent1]
        LEFT OUTER JOIN [dbo].[Appointment] AS [Extent2] ON [Extent1].[AppointmentId] = [Extent2].[Id]
        WHERE [Extent1].[PersonId] = @p__linq__0
    )  AS [Project1]
    **ORDER BY [Project1].[PersonId] ASC**
Run Code Online (Sandbox Code Playgroud)

我不明白需要将结果分组到另一个投影(Project1)中,而这似乎工作得很好:

SELECT 
        [Extent1].[Id] AS [Id], 
        [Extent1].[Read] AS [Read], 
        [Extent1].[Message] AS [Message], 
        [Extent1].[IssueDateUtc] AS [IssueDateUtc], 
        [Extent1].[AppointmentId] AS [AppointmentId], 
        [Extent1].[PersonId] AS [PersonId], 
        CASE WHEN ( NOT ((1 = [Extent2].[Status]) AND ([Extent2].[Status] IS NOT NULL))) THEN cast(1 as bit) WHEN (1 = [Extent2].[Status]) THEN cast(0 as bit) END AS [C1]
        FROM  [dbo].[Notification] AS [Extent1]
        LEFT OUTER JOIN [dbo].[Appointment] AS [Extent2] ON [Extent1].[AppointmentId] = [Extent2].[Id]
        WHERE [Extent1].[PersonId] = @p__linq__0
        **ORDER BY [Extent1].[PersonId] ASC**
Run Code Online (Sandbox Code Playgroud)

我发现 ef 和 linq 生成了大量有问题的 sql,我开始怀疑只编写原始 sql 是否会更好。

问题是:生成的 sql 额外代码位是否值得担心?为什么需要这个投影?

编辑以添加新的 linq

正如评论中提到的,冗长的内容可能是由后续运行的查询引起的。我重写了 linq 以仅使用一个查询对象,结果仍然相同:

dbSet.Where(n => n.PersonId == id).Select(n => new
            {
                Id = n.Id,
                AppointmentId = n.AppointmentId,
                Message = n.Message,
                Read = n.Read,
                CanBeDismissed = (n.Appointment.Status != AppointmentStatus.Waiting),
                IssueDate = n.IssueDateUtc
            }).OrderBy(n => n.Id).ToList();
Run Code Online (Sandbox Code Playgroud)

执行计划(两者相同sqls

实际执行计划

编辑2

刚刚通过简单的计数得到了这个查询。

dbSet.Count(x => x.Id == 1 && x.Read == false);

SELECT 
[GroupBy1].[A1] AS [C1]
FROM ( SELECT 
    COUNT(1) AS [A1]
    FROM [dbo].[Notification] AS [Extent1]
    WHERE ([Extent1].[PersonId] = 19) AND (0 = [Extent1].[Read])
)  AS [GroupBy1]
Run Code Online (Sandbox Code Playgroud)

预期的:

SELECT 
    COUNT(1) AS [A1]
    FROM [dbo].[Notification] AS [Extent1]
    WHERE ([Extent1].[PersonId] = 19) AND (0 = [Extent1].[Read])
Run Code Online (Sandbox Code Playgroud)

我不明白所有这些包装纸是从哪里来的以及为什么。

Ber*_*nig 3

我在我的机器上整理了一个小示例项目。实际上导致第一个示例中的投影的是 CanBeDismissed 字段的条件计算,从而导致CASE WHENSQL。如果您忽略这一点,实体框架将不会进行额外的投影。

因此,通过条件检查:

db.Notifications
    .Where(n => n.AppointmentId == 1)
    .OrderBy(n => n.Id)
    .Select(n => new
    {
        Id = n.Id,
        Message = n.Message,
        HasMessage = n.Message != null
    }).ToList();
Run Code Online (Sandbox Code Playgroud)

生成的 SQL 是:

SELECT 
    [Project1].[Id] AS [Id], 
    [Project1].[Message] AS [Message], 
    [Project1].[C1] AS [C1]
    FROM ( SELECT 
        [Extent1].[Id] AS [Id], 
        [Extent1].[Message] AS [Message], 
        CASE WHEN ([Extent1].[Message] IS NOT NULL) THEN cast(1 as bit) ELSE cast(0 as bit) END AS [C1]
        FROM [dbo].[Notifications] AS [Extent1]
        WHERE 1 = [Extent1].[AppointmentId]
    )  AS [Project1]
    ORDER BY [Project1].[Id] ASC
Run Code Online (Sandbox Code Playgroud)

让我添加生成的执行计划以供以后参考:

带有预测的执行计划

如果你忽略它:

db.Notifications
    .Where(n => n.AppointmentId == 1)
    .OrderBy(n => n.Id)
    .Select(n => new
    {
        Id = n.Id,
        Message = n.Message
    }).ToList();
Run Code Online (Sandbox Code Playgroud)

EF 不进行投影:

SELECT 
    [Extent1].[Id] AS [Id], 
    [Extent1].[Message] AS [Message]
    FROM [dbo].[Notifications] AS [Extent1]
    WHERE 1 = [Extent1].[AppointmentId]
    ORDER BY [Extent1].[Id] ASC
Run Code Online (Sandbox Code Playgroud)

这就是原因。这同样适用于您的count示例:如果发生任何分组,EF 将添加一个额外的投影,这使得查询更加详细。但重要的是,正如对您的问题的评论中所讨论的那样,它不会损害性能,无需担心这个额外的投影。

现在让我通过添加以下查询的执行计划来证明这一点,其中我刚刚从第一个查询中删除了投影并将 orderby 移至内部查询:

SELECT
[Extent1].[Id] AS [Id], 
[Extent1].[Message] AS [Message], 
CASE WHEN ([Extent1].[Message] IS NOT NULL) THEN cast(1 as bit) ELSE cast(0 as bit) END AS [C1]
FROM [dbo].[Notifications] AS [Extent1]
WHERE 1 = [Extent1].[AppointmentId]
ORDER BY [Extent1].[Id] ASC
Run Code Online (Sandbox Code Playgroud)

没有预测的执行计划

完全相同 - 没有添加额外的任务,并且成本分配保持不变。SQL 查询优化器将很好地优化此类预测。

再说一次,不要担心预测——它们不会伤害你,虽然我同意它们看起来而且有时不必要地冗长。但这里有两件事可能对你有帮助:

性能问题:

首先,如果您的查询遇到性能问题,请查看Clustered Index Scan您发布的执行计划中为什么会出现 a 。这并不总是某些索引问题的征兆,但很常见。您的问题可能根源于此。

摆脱不必要的预测:

如果您仍然想在所有(或至少更多)情况下摆脱这些投影,可以使用 Entity Framework Core 1.0 - 它实际上生成比 EF 6 更好的 SQL。可能值得考虑迁移到它,但请注意它不具备 EF 6 的所有功能,因此如果您使用 EF Core 1.0 不提供的功能,它可能不是一个选择。但它可以与完整的 .NET Framework 4.x 配合使用!

以下是当我执行答案的第一个 LINQ 语句时 EF Core 1.0 生成的示例:

SELECT [n].[Id], [n].[Message], CASE
    WHEN [n].[Message] IS NULL
    THEN CAST(0 AS BIT) ELSE CAST(1 AS BIT)
END
FROM [Notifications] AS [n]
WHERE ([n].[Id] = 1) AND ([n].[Id] = 1)
ORDER BY [n].[Id]
Run Code Online (Sandbox Code Playgroud)