2013-09-26 59 views
2

我有以下實體框架的查詢性能問題:實體框架總和()的性能

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

可能更改查詢一點點,使它看起來更加SQL十歲上下會做的伎倆:'context.Companies.Where(C => ...)選擇(X => x.DataFile.Sum(d => d.FileSize))。Single()'。這樣表達式訪問者可能會輕鬆一點,生成的SQL可能會更好;如果它沒有幫助,你可以在'Select'之後手動調用'AsEnumerable',看看是否改變了任何東西。 –

+0

謝謝。看我的編輯:) – davenewza

+0

@PatrykĆwiek正確的優化,但推理是不好的。問題歸結爲延遲加載。 – Aron

回答

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可能很聰明,只需從緩存中取出即可。

第二行引入該公司所有的數據文件(因爲List<T>沒有任何實體框架的代碼了。這意味着它拉下整個列表。

然後第三部分,你知道不.Sum()。但是如果你檢查實際的.Sum()實現,其實際上是IEnumerable.Sum(),這與Entity Framework沒有任何關係。簽名完全不同。

與ELinq作品之一是IQueryable<T>.Sum<T,TValue>(Expression<Func<T,TValue>> projection)和LINQ到對象之一是IEnumerable<T>.Sum<T,TValue>(Func<T.TValue> projection)

TLDR:

總之,它需要一些習慣,到哪裏工作LinqToEF開始和結束。你的代碼工作的唯一原因是EF懶惰加載。但是我會建議你在出現性能問題時關閉EF Lazy Loading,因爲它通常會隱藏對Linq的不足理解。

0

select語句應該實際上只是返回查詢定義了什麼,

select * from Extend1 where CompanyID = 16 

含義,不應該只返回你的所有行CompanyID = 16

TBH不知道如何實體框架的行爲,但如果你做一個查詢與NHibernate例如,LINQ查詢.First(p=>p.Id==16)會做一個選擇頂層(1)

也許這篇文章可以幫助您優化生成的查詢:Force Entity Framework to use SQL parameterization for better SQL proc cache reuse