2014-02-28 49 views
3

我注意到.Take()的位置對結果SQL沒有影響。例如說我有此查詢:.Take()的位置對生成的SQL沒有影響

IQueryable<Item> query = 
    db.Items.Where(i => i.CategoryID == categoryID && i.Deleted == false) 
      .OrderByDescending(i => i.ItemID).Skip(0); 

然後執行以下兩個查詢:

IQueryable<ItemViewModel> query1 = 
    query.Take(20).Select(i => new ItemViewModel { ItemID = i.ItemID }); 
IQueryable<ItemViewModel> query2 = 
    query.Select(i => new ItemViewModel { ItemID = i.ItemID }).Take(20); 

實體框架將生成兩個查詢以下相同的SQL:

SELECT TOP (20) 
    [Project1].[ItemID] AS [ItemID], 
FROM (SELECT [Project1].[ItemID] AS [ItemID], 
    row_number() OVER (ORDER BY [Project1].[ItemID] DESC) AS [row_number] 
    FROM (SELECT 
     [Filter1].[ItemID] AS [ItemID], 
     FROM (SELECT [Extent1].[ItemID] AS [ItemID], 
    [Extent1].[CategoryID] AS [CategoryID] 
    FROM [dbo].[Items] AS [Extent1] 
      WHERE 0 = [Extent1].[Deleted]) AS [Filter1] 
     WHERE ([Filter1].[CategoryID] = @p__linq__0) AND (@p__linq__0 IS NOT NULL) 
    ) AS [Project1] 
) AS [Project1] 
WHERE [Project1].[row_number] > 0 
ORDER BY [Project1].[ItemID] DESC 

爲什麼它這樣做?它不是應該爲子查詢中的TOP 20生成query1的以下內容嗎?

SELECT 
    [Project1].[ItemID] AS [ItemID] 
    FROM (SELECT [Project1].[ItemID] AS [ItemID] 
     FROM (SELECT TOP (20) 
      [Extent1].[ItemID] AS [ItemID], 
      row_number() OVER (ORDER BY [Extent1].[ItemID] DESC) AS [row_number] 
      FROM [dbo].[Items] AS [Extent1] 
      WHERE ([Extent1].[CategoryID] = @p__linq__0) AND (@p__linq__0 IS NOT NULL) 
      AND (0 = [Extent1].[Deleted]) AND [Extent1].[row_number] > 0 
      ORDER BY [Extent1].[ItemID] DESC 
     ) AS [Project1] 
    ) AS [Project1] 

我注意到,移動TOP 20內會增加時間從7秒在我的實際查詢即時響應,因爲它不TOP 20之前做的投影。

編輯:不幸的是,我似乎無法找到一種方法來強制實體框架做到這一點,因爲query.Take(20).Select(i => new { ... }).ToString() == query.Select(i => new { .. }).Take(20).ToString()。也許這是EF中的一個錯誤?

回答

4

在您提到的特定情況下,您提供的兩個LINQ查詢在功能上是等效的,因爲無論TakeSelect的排序如何,結果集總是相同的。

至於性能問題,這兩個查詢都將通過數據庫平臺進行優化。我會非常驚訝地發現兩者之間存在顯着差異。例如,我不希望第二個查詢對一大堆知道不會過去的項目執行投影,例如TOP。通常,LINQ查詢提供程序傾向於不專注於優化,只是因爲當SQL被轉換爲實際可執行代碼時,該步驟往往發生在數據庫級別。現在的數據庫已經花了相當多的精力來優化SQL代碼的編譯,所以查詢提供者根本就不需要重複這些工作。

但是,當您說過濾或排序時,它會更改查詢實際返回的內容。

query.Take(10).Where(someFilter); 

不(一定)返回同樣的事情:

query.Where(someFilter).Take(10); 

第一需要10項和回報然而,許多這些項目的通過過濾器。

第二個查詢最多返回10個全部通過過濾器的項目。

在你表現出兩個SQL查詢的情況下,他們在功能上是不同的因爲一個是排序在同時表然後抓住20個項目的每一個項目,另一個是抓住了第20個項目然後再訂購它們,這是多了一個的一個操作。

的情況下,對於Take的語義順序被正確地轉換成SQL是很重要的。在查詢提供者可以證明兩個給定操作的順序並不重要的情況下,不管它是否需要重新排序它都不是問題。

+0

這裏的問題是'Select()'。我希望Take()在選擇之前發生。在我的例子中,我一直在做'query.Where(filter)... .Take(10)'。出於某種原因'query.Select(r => new {..})。(10)'和query.Take(10).Select(r => new {..})是一樣的。我不希望SELECT在整個表上由SQL執行。在我的數據庫中,將「TOP」從外部移動到內部將使查詢時間從7秒減少到即時結果(在實際查詢中,在選擇內部發生了很多事情)。 –

+1

@dhsto如果你在結果上面,選擇不應該在整個數據庫上執行。如果真的如此,我會懷疑查詢提供者存在問題。如果無法進行優化,無論出於何種原因,那麼這並不會讓您有太多的選擇,除了不使用LINQ並自己編寫SQL。 – Servy

+1

@dhsto在Management Studio中運行sql並查看[Execution Plan](執行計劃)(http://stackoverflow.com/questions/758912/how-to-read-an-execution-plan-in-sql-server) 。您將看到實際發生的排序,這是Servy在談論的數據庫引擎爲您執行的重新排序。看看[我的dba網站的這個問題](http://dba.stackexchange.com/questions/29884/execution-plan-flips-filter-and-execute-scalar-when-using-the-pk -causes-cast-to)在where子句中添加一個'和'來改變執行計劃並導致一個轉換錯誤。 –

1

答案其實很簡單。你基本上比較這兩個查詢:

select top 20 ItemId 
from Items 
order by ItemId desc 

到:

select ItemId 
from (
    select top 20 ItemId 
    from Items 
) p 
order by ItemId descending 

後者將被隨機挑選20行而非20行最高ItemIds這就是爲什麼您預期的但沒有得到的查詢更快:這是不正確的,並且由數據庫運行要簡單得多。

+0

當我將'TOP 20'移動到生成的SQL子查詢中時,我忘了也移動了ORDER BY。在實際的C#代碼中,我正在執行'.OrderByDescending ()'在'Take()'之前' –

+0

我同意你不會讓實體框架在子查詢中放置'top'和'order by',因爲它是等價的,不應該在哪裏他們去在這種情況下,我很懷疑你會看到一個差異在性能上的兩種方法之間,一旦他們正在做同樣的事情。 –

+0

當我也動'ORDER BY'到生成的子查詢它仍然會立即發生。我不認爲對整個表的主鍵排序是問題。我認爲這個問題是投影在子查詢對整個表進行。 –