LINQ 嵌套组性能

LINQ nested groups performance

我有一个完整的外部连接查询从 sql 紧凑型数据库中提取数据(我使用 EF6 进行映射):

        var query =
            from entry in left.Union(right).AsEnumerable()
            select new
            {
                ...
            } into e
            group e by e.Date.Year into year
            select new
            {
                Year = year.Key,
                Quartals = from x in year
                           group x by (x.Date.Month - 1) / 3 + 1 into quartal
                           select new
                           {
                               Quartal = quartal.Key,
                               Months = from x in quartal
                                        group x by x.Date.Month into month
                                        select new
                                        {
                                            Month = month.Key,
                                            Contracts = from x in month
                                                        group x by x.Contract.extNo into contract
                                                        select new
                                                        {
                                                            ExtNo = month.Key,
                                                            Entries = contract,
                                                        }
                                        }
                           }
            };

如您所见,我使用嵌套组来构建结果。 有趣的是,如果我删除 AsEnumerable() 调用,查询将花费 3.5 倍的时间来执行:~210ms vs ~60ms。当它第一次运行时,差异要大得多:39000(!)ms vs 1300ms。

我的问题是:

  1. 我做错了什么,也许这些分组应该以不同的方式完成?

  2. 为什么第一次执行需要那么多时间?我知道应该构建表达式树等,但是 39 秒?

  3. 在我的例子中,为什么 linq to db 比 linq to entities 慢?如果可能的话,在处理之前从数据库加载数据通常更慢并且更好吗?

感谢!

使用 AsEnumerable() 会将实现 IEnumerable<T> 的类型转换为 IEnumerable<T> 本身。

阅读此主题https://msdn.microsoft.com/en-us/library/bb335435.aspx

AsEnumerable<TSource>(IEnumerable<TSource>) 可用于在序列实现 IEnumerable<T> 但也有一组不同的 public 查询方法可用时在查询实现之间进行选择。例如,给定一个泛型 class Table,它实现了 IEnumerable<T> 并且有自己的方法,例如 WhereSelectSelectMany,一个调用 Where 将调用 Table 的 public Where 方法。表示数据库 table 的 Table 类型可以有一个 Where 方法,该方法将谓词参数作为表达式树并将树转换为 SQL 以便远程执行。如果不需要远程执行,例如因为谓词调用本地方法,可以使用 AsEnumerable<TSource> 方法隐藏自定义方法,而不是使标准查询运算符可用。

当您首先调用 AsEnumerable() 时,它不会将 LINQ 转换为 SQL,而是在 Where 枚举时将 table 加载到内存中它。由于现在它已加载到内存中,因此执行速度更快。

回答你的三个问题:

Maybe those groupings should be done in a different way?

没有。如果你想要嵌套分组,你只能通过分组内的分组来实现。

可以一次按多个字段分组:

from entry in left.Union(right)
select new
{
    ...
} into e
group e by new 
           { 
               e.Date.Year, 
               Quartal = (e.Date.Month - 1) / 3 + 1, 
               e.Date.Month, 
               contract = e.Contract.extNo 
           } into grp
select new
{
    Year = grp.Key,
    Quartal = grp.Key,
    Month = grp.Key,
    Contracts = from x in grp
                select new
                {
                    ExtNo = month.Key,
                    Entries = contract,
                }
}

这将从生成的查询中消除很多复杂性,因此它可能(非常)快 没有 AsEnumerable()。但结果却大不相同:扁平组(年、季度等,在一行中),而不是嵌套分组。

  1. Why the first execution takes so much time?

因为生成的 SQL 查询可能非常复杂,数据库引擎的查询优化器找不到快速执行路径。

3a. Why is linq to db slower than linq to entities in my case?

因为,显然,在这种情况下,首先将数据提取到内存中并通过 LINQ-to-objects 进行分组要高效得多。如果 leftright 代表或多或少复杂的查询本身,这种影响将更加显着。在那种情况下,生成的 SQL 会变得非常臃肿,因为它必须在一个语句中处理两个复杂来源,这可能会导致许多重复的相同子查询。通过外包分组,数据库可能只剩下一个相对简单的查询,当然内存中的分组永远不会受到 SQL 查询的复杂性的影响。

3b. Is it generally slower and its better to load data from db if possible before processing?

不,一般不会。我什至会说,几乎没有。在这种情况下,这是因为(如我所见)您没有过滤数据。但是,如果 AsEnumerable() 之前的部分将 return 数百万条记录,而您随后将应用过滤,则没有 AsEnumerable() 的查询可能会快得多,因为过滤是在数据库中完成的。

因此,您应该始终关注生成的 SQL。期望 EF 始终生成超级优化的 SQL 语句是不现实的。它几乎永远不会。它的主要重点是正确性(并且它在这方面做得非常出色),性能是次要的。让 LINQ-to-Entities 和 LINQ-to-object 作为一个巧妙的团队协同工作是开发人员的工作。