我遇到以下Entity Framework查询的性能问题:
using (MyEntities context = new MyEntities())
{
return context.Companies
.Single(c => c.CompanyId == company.CompanyId)
.DataFile.Sum(d => d.FileSize);
}
在SQL事件探查器中进行跟踪时,我看到以下SQL命令:
exec sp_executesql N'SELECT
[Extent1].[DataFileID] AS [DataFileID],
[Extent1].[LocalFileName] AS [LocalFileName],
[Extent1].[ServerFileName] AS [ServerFileName],
[Extent1].[DateUploaded] AS [DateUploaded],
[Extent1].[FileSize] AS [FileSize],
[Extent1].[CompanyID] AS [CompanyID]
FROM [dbo].[DataFile] AS [Extent1]
WHERE [Extent1].[CompanyID] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=16
从我所看到的,所有数据文件行都被返回(超过10,000)到内存中,然后Sum()
正在发生。
修改
根据Patryk的建议,我已将查询更改为:
using (MyEntities context = new MyEntities())
{
return context.Companies
.Where(c => c.CompanyId == company.CompanyId)
.Select(x => x.DataFiles.Sum(d => d.FileSize))
.Single();
}
SQL跟踪如下所示:
SELECT TOP (2)
(
SELECT
SUM([Extent2].[FileSize]) AS [A1]
FROM
[dbo].[DataFile] AS [Extent2]
WHERE
[Extent1].[CompanyId] = [Extent2].[CompanyID]
) AS [C1]
FROM
[dbo].[Company] AS [Extent1]
WHERE
[Extent1].[CompanyId] = 16
这要好得多,但是,基本上我只想要这样简单快捷的东西:
SELECT SUM(FileSize) FROM DataFile WHERE CompanyId = 16
答案 0 :(得分:5)
首先......自上次检查以来,实体框架都有所改进。所有帐户的表达式.Single(c => c.CompanyId == company.CompanyId)
都应该失败,因为实体框架应该在Expression.Constant<Company>
上失败。我怀疑你实际上已经混淆了你的代码清单。
这有点错误的原因是.Single(Expression)
的工作原理。与大多数Linq IQueryable<T>
扩展方法不同,它会立即进行评估。
using (MyEntities context = new MyEntities())
{
return context.Companies
.Single(c => c.CompanyId == company.CompanyId)
.DataFile.Sum(d => d.FileSize);
}
相当于
using (MyEntities context = new MyEntities())
{
Company company = context.Companies.Single(c => c.CompanyId == company.CompanyId);
List<DataFile> dataFiles = company.DataFile
return dataFiles.Sum(d => d.FileSize);
}
为你解决这个问题。表现不佳来自多个方面。
第一个是.Single()
强制对查询进行评估,返回Company
(您之后,但不需要)。如果幸运的话,EF可能很聪明,只需从缓存中提取即可。
第二行提取该公司的所有DataFiles(因为List<T>
中没有任何实体框架代码。这意味着它可以下拉整个列表。
然后你知道的第三部分是.Sum()
。但是,如果您检查实际的.Sum()
实现,它实际上是IEnumerable.Sum()
,它与实体框架无关。签名完全不同。
与ELinq合作的是IQueryable<T>.Sum<T,TValue>(Expression<Func<T,TValue>> projection)
,而Linq to Object one是IEnumerable<T>.Sum<T,TValue>(Func<T.TValue> projection)
TLDR:
简而言之,需要一些工作来解决LinqToEF开始和结束的问题。您的代码工作的唯一原因是EF Lazy加载。但是,如果存在性能问题,我会建议你关闭EF Lazy Loading,因为它常常掩盖对Linq的不了解。
答案 1 :(得分:0)
select语句实际上应该只返回查询定义的内容,
select * from Extend1 where CompanyID = 16
意思是,不应该只返回CompanyID = 16的所有行。
Tbh不知道实体框架的行为,但是如果你用nhibernate进行查询,例如linq查询.First(p=>p.Id==16)
会做一个Select Top(1)
也许这篇文章可以帮助您优化生成的查询:Force Entity Framework to use SQL parameterization for better SQL proc cache reuse