LINQ嵌套组性能

时间:2015-01-30 11:29:13

标签: c# performance linq entity-framework grouping

我有一个完整的外连接查询从sql compact数据库中提取数据(我使用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慢于实体?它是否通常较慢,如果可能,在处理之前从db加载数据会更好?

  4. thakns!

2 个答案:

答案 0 :(得分:2)

回答你的三个问题:

  

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

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

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

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. 为什么第一次执行需要这么多时间?
  2.   

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

  

3A。在我的情况下,为什么linq比linq更慢于实体?

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

  

3B。它是否通常较慢,如果可能,在处理之前从db加载数据会更好?

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

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

答案 1 :(得分:1)

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

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

当序列实现AsEnumerable<TSource>(IEnumerable<TSource>)但也有一组不同的公共查询方法可用时,

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

首先调用AsEnumerable<TSource>时,它不会转换LINQ-to-SQL,而是在AsEnumerable()枚举它时将表加载到内存中。从现在开始加载到内存中,它的执行速度更快。