Linq to Entities 复杂度

Linq to Entities complexity

我从 Linq to entities 开始,也许有人可以阐明一些问题。

我有两个 table - Vizite(父 table)和 AngajatiVizite(子 table)。我首先使用数据库,所以我使用 Vizite.Id 和 AngajatiVizite.IdVizita.

创建了它们之间的关系

我需要从 Vizite 获取行和一个位字段,如果 DataStartDataEnd 字段为空或子记录的计数来自 AngajatiVizite 为零。就是这样,如果 Vizite 有零个从属记录或任何 Data#### 字段为空,则计算字段为 0。

到目前为止一切顺利,我使用的 linq 工作正常。我使用的语法是这个:

 var list = ctx.Vizite
                .OrderBy(p => p.DataEnd != null && p.DataStart != null && p.AngajatiVizite.Count > 0)
                .ThenBy(p => p.Data)
                .Select(p => new
                {
                    p.Id,
                    p.Numar,
                    p.Data,
                    p.DataStart,
                    p.DataEnd,
                    Programat = p.DataEnd != null && p.DataStart != null && p.AngajatiVizite.Count > 0
                })
                .ToList();

Linq 生成的sql 命令非常复杂,我不明白为什么它必须那么 复杂,有什么区别。

我从 linq 得到的是这样的:

SELECT 
[Project6].[Numar] AS [Numar], 
[Project6].[Id] AS [Id], 
[Project6].[Data] AS [Data], 
[Project6].[DataStart] AS [DataStart], 
[Project6].[DataEnd] AS [DataEnd], 
[Project6].[C2] AS [C1]
FROM ( SELECT 
    [Project5].[C1] AS [C1], 
    [Project5].[Id] AS [Id], 
    [Project5].[Numar] AS [Numar], 
    [Project5].[Data] AS [Data], 
    [Project5].[DataStart] AS [DataStart], 
    [Project5].[DataEnd] AS [DataEnd], 
    CASE WHEN ([Project5].[C2] > 0) THEN cast(1 as bit) WHEN ( NOT ([Project5].[C3] > 0)) THEN cast(0 as bit) END AS [C2]
    FROM ( SELECT 
        [Project4].[C1] AS [C1], 
        [Project4].[Id] AS [Id], 
        [Project4].[Numar] AS [Numar], 
        [Project4].[Data] AS [Data], 
        [Project4].[DataStart] AS [DataStart], 
        [Project4].[DataEnd] AS [DataEnd], 
        [Project4].[C2] AS [C2], 
        (SELECT 
            COUNT(1) AS [A1]
            FROM [dbo].[AngajatiVizite] AS [Extent5]
            WHERE [Project4].[Id] = [Extent5].[IdVizita]) AS [C3]
        FROM ( SELECT 
            [Project3].[C1] AS [C1], 
            [Project3].[Id] AS [Id], 
            [Project3].[Numar] AS [Numar], 
            [Project3].[Data] AS [Data], 
            [Project3].[DataStart] AS [DataStart], 
            [Project3].[DataEnd] AS [DataEnd], 
            (SELECT 
                COUNT(1) AS [A1]
                FROM [dbo].[AngajatiVizite] AS [Extent4]
                WHERE [Project3].[Id] = [Extent4].[IdVizita]) AS [C2]
            FROM ( SELECT 
                CASE WHEN ([Project2].[C1] > 0) THEN cast(1 as bit) WHEN ( NOT ([Project2].[C2] > 0)) THEN cast(0 as bit) END AS [C1], 
                [Project2].[Id] AS [Id], 
                [Project2].[Numar] AS [Numar], 
                [Project2].[Data] AS [Data], 
                [Project2].[DataStart] AS [DataStart], 
                [Project2].[DataEnd] AS [DataEnd]
                FROM ( SELECT 
                    [Project1].[Id] AS [Id], 
                    [Project1].[Numar] AS [Numar], 
                    [Project1].[Data] AS [Data], 
                    [Project1].[DataStart] AS [DataStart], 
                    [Project1].[DataEnd] AS [DataEnd], 
                    [Project1].[C1] AS [C1], 
                    (SELECT 
                        COUNT(1) AS [A1]
                        FROM [dbo].[AngajatiVizite] AS [Extent3]
                        WHERE [Project1].[Id] = [Extent3].[IdVizita]) AS [C2]
                    FROM ( SELECT 
                        [Extent1].[Id] AS [Id], 
                        [Extent1].[Numar] AS [Numar], 
                        [Extent1].[Data] AS [Data], 
                        [Extent1].[DataStart] AS [DataStart], 
                        [Extent1].[DataEnd] AS [DataEnd], 
                        (SELECT 
                            COUNT(1) AS [A1]
                            FROM [dbo].[AngajatiVizite] AS [Extent2]
                            WHERE [Extent1].[Id] = [Extent2].[IdVizita]) AS [C1]
                        FROM [dbo].[Vizite] AS [Extent1]
                    )  AS [Project1]
                )  AS [Project2]
            )  AS [Project3]
        )  AS [Project4]
    )  AS [Project5]
)  AS [Project6]

当我真正需要的是这个:

Select
Vizite.Id
, Vizite.Numar
, Vizite.Data
, Vizite.DataStart
, Vizite.DataEnd
, Case
    When DataStart != Null And DataEnd != Null And (Select Count(Id) From AngajatiVizite Where Vizite.Id = AngajatiVizite.IdVizita) > 0 Then 1
    Else 0
End As Programat
From Vizite
Order By Programat, Data

任何人都可以向我解释为什么生成的 SQL 是如此复杂,甚至几乎不可能通过简单阅读 sql 语法来弄明白吗?

谢谢

如果执行以下操作,SQL 语句的复杂性会发生什么变化?

var list = ctx.Vizite
    .Select(p => new
    {
        p.Id,
        p.Numar,
        p.Data,
        p.DataStart,
        p.DataEnd,
        Programat =
            p.DataEnd != null && p.DataStart != null && p.AngajatiVizite.Count > 0
    }
    .OrderBy(p => p.Programat)
    .ThenBy(p => p.Data)
    .ToList();

我希望通过不重复 p.DataEnd != null && p.DataStart != null && p.AngajatiVizite.Count > 0 并将 OrderByThenBy 移到 select 之后,您会得到一个更简单的查询。


编辑

为了进一步简化 SQL,您可以选择在从数据库中获取原始数据后做一些工作:

var list = ctx.Vizite
    .Select(p => new
    {
        p.Id,
        p.Numar,
        p.Data,
        p.DataStart,
        p.DataEnd,
        AngajatiViziteCount = p.AngajatiVizite.Count
    }
    .AsEnumerable() // do the rest of the work using LINQ to objects
    .OrderBy(p => p.DataEnd != null && p.DataStart != null && p.AngajatiViziteCount > 0)
    .ThenBy(p => p.Data)
    .ToList();

Entity Framework 不会构建漂亮的查询,这是事实;有时这很麻烦,因为很难追踪 SQL 回溯到 LINQ 语句。

不过,如果查询计划优化器不知道如何处理它们,那将是一个问题。幸运的是,当涉及 Sql 服务器时,EF 团队已经设法使 SQL 在自 EF5 以来的每个版本中都可以更好地优化。所以一般来说你不应该太担心它,只有当性能比合理预期的更差时才开始研究它。

虽然有一些经验法则。其中之一是只计算一次计算值。这是 let 关键字派上用场的地方:

var list = (from p in ctx.Vizite
            let Programat = p.DataEnd != null && p.DataStart != null
                         && p.AngajatiVizite.Count > 0
            order by Programat, p.Data
            select new
            {
                p.Id,
                p.Numar,
                p.Data,
                p.DataStart,
                p.DataEnd,
                Programat
            }).ToList();

这在 LINQ 查询语法中运行良好。在流利的(方法)语法中,您可以做完全相同的事情,但这需要两个后续的 Select 语句。