【问题标题】:LINQ nested groups performanceLINQ 嵌套组性能
【发布时间】:2015-01-30 11:29:12
【问题描述】:

我有一个完整的外部联接查询从 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 entity 慢?如果可能,在处理之前从 db 加载数据通常会更慢且更好吗?

谢谢!

【问题讨论】:

  • AsEnumerable 在分组之前将数据带入内存。删除它意味着对数据库运行多个子查询,导致它变慢。

标签: c# performance linq entity-framework grouping


【解决方案1】:

回答你的三个问题:

也许这些分组应该以不同的方式完成?

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

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

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()。但结果完全不同:一个扁平组(一行中的年份、Quartal 等),而不是嵌套分组。

  1. 为什么第一次执行需要这么长时间?

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

3a。在我的情况下,为什么 linq to db 比 linq to entity 慢?

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

3b。如果可能,在处理之前从 db 加载数据通常会更慢且更好吗?

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

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

【讨论】:

  • 对了,下次最好一次问一个问题。 StackOverflow 喜欢有可以以一种方式回答的问题,即一个答案可能会冒泡为 the 答案。如果三个人能回答你的三个问题,那就很难了。
【解决方案2】:

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-11-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多