【问题标题】:Entity Framework Sum() performance实体框架 Sum() 性能
【发布时间】:2014-08-22 15:31:33
【问题描述】:

我在使用以下实体框架查询时遇到性能问题:

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

【问题讨论】:

  • 也许稍微改变一下查询,让它看起来更像 SQL-ish 就可以了:context.Companies.Where(c => ...).Select(x => x.DataFile.Sum(d => d.FileSize)).Single()。这样,表达式访问者可能会更容易一点,生成的 SQL 可能会更好;如果没有帮助,您可以在Select 之后手动调用AsEnumerable,看看是否有任何改变。
  • 谢谢。查看我的编辑:)
  • @PatrykĆwiek 正确优化,但推理很糟糕。问题归结为延迟加载。
  • 如果你想要那个确切的查询而不是只使用context.Companies.SqlQuery("...") - 我怀疑这两个查询之间的性能差异很大。
  • @Aron 在这种特殊情况下 - 是的,在调用 Single 之后,我们会收到一个延迟加载的集合。当您将 LINQ 查询重新构造为更像 SQL 时,通常可以避免很多问题 - 我们不能忘记它在某一时刻被转换为 SQL。

标签: c# .net linq entity-framework entity-framework-5


【解决方案1】:

首先...自从我上次检查以来,任何一个实体框架都得到了改进。表达式.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 可能会很聪明,只需从缓存中提取即可。

第二行提取该公司的所有数据文件(因为List&lt;T&gt; 中没有任何实体框架代码。这意味着它必须拉下整个列表。

然后你知道的第三部分是.Sum()。但是如果你检查实际的.Sum() 实现,它实际上是IEnumerable.Sum(),这与实体框架无关。签名完全不同。

与 ELinq 一起使用的是 IQueryable&lt;T&gt;.Sum&lt;T,TValue&gt;(Expression&lt;Func&lt;T,TValue&gt;&gt; projection),而 Linq to Object 一是 IEnumerable&lt;T&gt;.Sum&lt;T,TValue&gt;(Func&lt;T.TValue&gt; projection)

TLDR:

简而言之,确定 LinqToEF 的开始和结束位置需要一些时间。您的代码有效的唯一原因是 EF 延迟加载。但我建议您在出现性能问题时关闭 EF Lazy Loading,因为它通常会隐藏对 Linq 的不了解。

【讨论】:

    【解决方案2】:

    select 语句实际上应该只返回查询定义的内容,

    select * from Extend1 where CompanyID = 16
    

    意思是,它应该只返回 CompanyID = 16 的所有行。

    Tbh 不知道实体框架的行为方式,但如果您使用 nhibernate 进行查询,例如,linq 查询 .First(p=&gt;p.Id==16) 将执行 Select Top(1)

    也许这篇文章可以帮助您优化生成的查询:Force Entity Framework to use SQL parameterization for better SQL proc cache reuse

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-12-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-02
      • 2015-11-25
      • 2011-11-16
      • 1970-01-01
      相关资源
      最近更新 更多