我有一个完整的外连接查询从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。
我的问题是:
我做错了什么,也许这些分组应该以不同的方式完成?
为什么第一次执行需要这么多时间?我知道应该建立表达式树等,但是39秒?
为什么在我的情况下linq to db比linq慢于实体?它是否通常较慢,如果可能,在处理之前从db加载数据会更好?
thakns!
答案 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()
的情况下,它可能(更快)。但结果却截然不同:一个扁平组(年份,四分之一等),而不是嵌套分组。
- 为什么第一次执行需要这么多时间?
醇>
因为生成的SQL查询可能非常复杂,并且数据库引擎的查询优化器无法找到快速执行路径。
3A。在我的情况下,为什么linq比linq更慢于实体?
因为,显然,在这种情况下,首先将数据提取到内存并通过LINQ到对象进行分组会更有效。如果left
和right
代表或多或少复杂的查询本身,则此效果会更为显着。在这种情况下,生成的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>
,Where
和Select
)的通用类SelectMany
,调用{{ 1}}将调用Where
的公共Where
方法。表示数据库表的Table
类型可以具有Table
方法,该方法将谓词参数作为表达式树并将树转换为SQL以进行远程执行。如果不需要远程执行,例如因为谓词调用本地方法,Where
方法可用于隐藏自定义方法,而是使标准查询运算符可用。
首先调用AsEnumerable<TSource>
时,它不会转换LINQ-to-SQL,而是在AsEnumerable()
枚举它时将表加载到内存中。从现在开始加载到内存中,它的执行速度更快。