2015-07-21 76 views
8

我對一個實體的簡單分頁LINQ查詢:實體框架生成低效SQL對分頁查詢

var data = (from t in ctx.ObjectContext.Widgets 
      where t.CampaignId == campaignId && 
       t.CalendarEventId == calendarEventId 
       (t.RecurringEventId IS NULL OR t.RecurringEventId = recurringEventId) 
      select t); 

data = data.OrderBy(t => t.Id); 

if (page > 0) 
{ 
    data = data.Skip(rows * (page - 1)).Take(rows); 
} 

var l = data.ToList(); 

我預期產生類似SQL:

select top 50 * from Widgets w where CampaignId = xxx AND CalendarEventId = yyy AND (RecurringEventId IS NULL OR RecurringEventId = zzz) order by w.Id 

當我運行上面在SSMS中查詢,它會很快返回(必須先重建我的索引)。

但是,生成的SQL是不同的。它包含一個嵌套查詢,如下所示:

SELECT TOP (50) 
[Project1].[Id] AS [Id], 
[Project1].[CampaignId] AS [CampaignId] 
<redacted> 
FROM (SELECT [Project1].[Id] AS [Id], 
[Project1].[CampaignId] AS [CampaignId], 
<redacted>, 
row_number() OVER (ORDER BY [Project1].[Id] ASC) AS [row_number] 
    FROM (SELECT 
     [Extent1].[Id] AS [Id], 
     [Extent1].[CampaignId] AS [CampaignId], 
     <redacted> 
     FROM [dbo].[Widgets] AS [Extent1] 
     WHERE ([Extent1].[CampaignId] = @p__linq__0) AND ([Extent1].[CalendarEventId] = @p__linq__1) AND ([Extent1].[RecurringEventId] = @p__linq__2 OR [Extent1].[RecurringEventId] IS NULL) 
    ) AS [Project1] 
) AS [Project1] 
WHERE [Project1].[row_number] > 0 
ORDER BY [Project1].[Id] ASC 

小部件表是巨大的和內查詢返回的記錄100000s,引起超時。

有什麼我可以做的改變世代?我做錯了什麼?

UPDATE

我終於成功地重構我的代碼相對快速地返回結果:

var data = (from t in ctx.ObjectContext.Widgets 
      where t.CampaignId == campaignId && 
       t.CalendarEventId == calendarEventId 
       (t.RecurringEventId IS NULL OR t.RecurringEventId = recurringEventId) 
      select t)).AsEnumerable().Select((item, index) => new { Index = index, Item = item }); 

      data = data.OrderBy(t => t.Index); 

      if (page > 0) 
      { 
       data = data.Where(t => t.Index >= (rows * (page - 1))); 
      } 

      data = data.Take(rows); 

注意,page > 0邏輯僅僅是用來防止使用無效的參數;它沒有優化。實際上,page > 1雖然有效,但不會爲第1頁提供任何明顯的優化;因爲Where不是一個緩慢的操作。

+2

您能顯示查詢計劃?我不明白爲什麼內部查詢會在這裏完整檢索。 SQL執行的方式有問題。 –

+1

將'order by'添加到查詢中時速度有多快?即'選擇頂部50 *的小部件,其中CampaignId = xxx和CalendarEventId = yyy按ID排序' – Aducci

+1

您的快速SQL沒有ORDER BY。如果你添加了會發生什麼? – hvd

回答

1

之前的SQL Server 2012中,生成的SQL代碼是執行分頁的最佳方式。是的,它是非常有效而且效率非常低的,但是即使是手工編寫自己的SQL scritp也是最好的。網絡上有大量的數字墨水。只是谷歌它。

在firt頁面中,可以優化不做Skip,只是Take,但是在任何其他頁面你都可以做到。

workarround可能會在持久性中生成自己的row_number(自動身份識別可以工作),只需在代碼中執行where(widget.number > (page*rows)).Take(rows)。如果您的widget.number中有一個好的索引,查詢應該非常快。 但是,這打破了動態orderBy

但是,我可以在您的代碼中看到您總是按widget.id排序;所以,如果動態orderBy不是必需的,這可能是一個有效的解決方法。

你會服用自己的藥嗎?

你能問我嗎。

不,我不會。處理這個問題的最好方法是使用持久性讀取模型,甚至可以爲每個窗口小部件orderBy字段使用自己的widget.number。問題在於,爲這個問題建立一個持久性讀模型的系統太瘋狂了。閱讀模型是系統總體設計的一部分,需要從設計和開發系統的一開始就考慮到它。

+0

我認爲LINQ中的自動標識就是答案。將發佈更新,如果它的工作。 – Kev

1

生成的查詢非常複雜並且嵌套,因爲您使用了Skip方法。在T-SQL Take中,只需使用Top即可輕鬆實現,但Skip不是這種情況 - 要應用它,您需要row_number,這就是爲什麼存在嵌套查詢的原因 - inner返回row_number行,外層過濾它們以獲取正確行數。您的查詢:

select top 50 * from Widgets w where CampaignId = xxx AND CalendarEventId = yyy AND (RecurringEventId IS NULL OR RecurringEventId = zzz) order by w.Id 

缺少跳過初始行。爲了保持查詢的效率,最好不要使用Take和Skip來通過Id條件保持分頁,因爲您正在基於該字段命令行進行分頁:

var data = (from t in ctx.ObjectContext.Widgets 
     where t.CampaignId == campaignId && 
      t.CalendarEventId == calendarEventId 
      (t.RecurringEventId IS NULL OR t.RecurringEventId = recurringEventId) 
     select t); 

data = data 
    .OrderBy(t => t.Id); 
    .Where(t => t.Id >= rows * (page - 1) && t.Id < rows * page) 
    .ToList(); 
+0

我不認爲這會有所幫助。匹配的Widgets的ID不會從1開始,並且Ids中可能存在空白。 – Kev