[英]Entity Framework generates inefficient SQL for paged query
我對一個實體有一個簡單的分頁linq查詢:
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
Widgets表非常龐大,內部查詢返回100000s條記錄,從而導致超時。
我可以做些什么來改變世代嗎? 我做錯了什么?
更新
我終於設法重構我的代碼以相對快速地返回結果:
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
雖然有效,但對第一頁沒有提供任何明顯的優化; 因為在Where
操作不是很慢。
在SQL Server 2012之前的版本中,生成的SQL代碼是執行分頁的最佳方法。 是的,它很糟糕並且效率很低,但是即使您手動編寫自己的SQL scritp,也可以做到最好。 網上有大量關於此的數字墨水。 只是谷歌它。
在firt頁面中,可以優化此方法,而不是Skip
和僅執行Take
而在其他任何頁面中都可以進行優化。
一個工作循環可能是在持久性上生成您自己的row_number(可以使用自動標識),然后執行where(widget.number > (page*rows) ).Take(rows)
。在代碼中執行where(widget.number > (page*rows) ).Take(rows)
。 如果您的widget.number
有良好的索引,則查詢應該非常快。 但是 ,這破壞了動態orderBy
。
但是,我可以在您的代碼中看到您始終按widget.id
排序; 因此,如果動態orderBy
不是必需的,則這可能是有效的解決方法。
你會自己吃葯嗎?
你能問我嗎。
不我不會。 解決此問題的最佳方法是擁有一個持久性讀取模型,您甚至可以為每個小部件orderBy字段只有一個表及其自己的widget.number
。 問題在於僅針對此問題使用持久性讀取模型對系統進行建模太瘋狂了。 擁有讀取模型是系統總體設計的一部分,並且需要從系統設計和開發的開始就考慮到這一點。
生成的查詢是如此復雜且嵌套,因為您使用了Skip方法。 在T-SQL中,僅使用Top即可輕松實現,但Skip並非如此-要應用它,您需要row_number,這就是為什么要使用嵌套查詢的原因-內部返回具有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 and 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();
AFAIK,您無法更改由實體生成的查詢。 盡管您可以強制實體運行原始SQL查詢:
https://msdn.microsoft.com/zh-CN/data/jj592907.aspx
您還可以使用存儲過程:
https://msdn.microsoft.com/zh-CN/data/gg699321.aspx
即使有機會更改生成的查詢IMO,它也會隨處可見。 我敢打賭,將更容易自己編寫SQL查詢。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.