背景
最近在使用EFCore2.2進行查詢后並排序的過程中發現了一個問題,就是查詢的過程使用了Include后代碼生成的SQL不符合預期,並且性能上面有很大的問題,借此寫一篇文章來進行分析。
1.1 EFCore語句
LinQ語句 _ = _repairContractRepository.GetAll().Include(r => r.RepairContractWorkItems).OrderByDescending(r => r.CreateTime).Take(20).ToList();
在上面的例子中_repairContractRepository表示的是實體RepairContract的倉儲,在代碼中的定義是:private readonly IRepository<RepairContract, Guid> _repairContractRepository;另外一個RepairContract對應多個RepairContractWorkItem,兩個實體之間是一對多的關系,在EFCore中通過Include方法來一次性查詢出關聯的對象,后面我們將查詢的結果按照主單的CreateTime進行排序並取前20條,我們來看看這條語句生成的SQL是否符合預期。
1.2 生成的SQL語句
SELECT TOP (20) r.* FROM [RepairContract] AS [r] ORDER BY [r].[CreateTime] DESC, [r].[Id]
明明只是按照CreateTime進行排序,為什么后面會加一個[r].[Id],這個問題該怎么解釋,多了一個按照主單Id升序排列到底有什么影響,我們來看看具體的統計信息。
圖一 加了導航屬性生成SQL及部分參數統計
在我們的數據庫中主單RepairContract大概有26萬條數據,我們已經在CreateTime上面創建了索引,按照道理不會這么慢,那我們試着去掉[r].[Id]來看看到底有什么差別。
圖二 未加導航屬性生成SQL及部分參數統計
結果查詢的時間從5723毫秒一下子降到了3毫秒,性能產生了巨大的提升,為什么會產生這個原因呢?事后發現是因為在查詢的過程中使用了導航屬性Include,去掉這個導航屬性之后就能夠生成符合我們預期的SQL語句了,所以在使用Include查詢並進行排序的過程中一定要注意看最終生成的SQL是否符合預期,至於為什么加了導航屬性后進行排序會多出一部分排序,這個也沒有發現原因。
結論
其實對於這個問題早在EF框架的時候就有這個問題,你可以看看這篇文章,現階段的解決方式只有在使用導航屬性后盡量不要去Order By操作,如果真要進行排序操作,那么就不要使用Include的導航屬性來直接進行操作,這個在使用EFCore2.2版本的時候需要特別注意。