分頁
分頁是指擷取頁面的結果,而不是一次擷取全部結果;這種作法通常針對大量結果集執行,在顯示的使用者介面中,使用者可以瀏覽上一頁或下一頁的結果。
警告
不論使用何種分頁方法,請務必確保排序完全不重複。 舉例來說,如果結果的排序依據只有日期,但相同日期可能有多個結果,則結果可能會在分頁時遭到略過,因為這些結果在兩個分頁查詢中的排序不同。 同時依據日期和識別碼 (或任何其他唯一屬性或屬性組合) 排序,才能讓排序完全不重複並避免上述問題。 請注意,關聯式資料庫預設不會套用任何排序,對於主索引鍵也一樣。
注意
Azure Cosmos DB 有自己的分頁機制,請參閱專門文件頁面。
位移分頁
使用資料庫實作分頁的常見方式是使用 Skip
和 Take
LINQ 運算子 (SQL 的 OFFSET
和 LIMIT
)。 假設頁面大小為 10 個結果,您可以使用 EF Core 擷取第三頁,如下所示:
var position = 20;
var nextPage = context.Posts
.OrderBy(b => b.PostId)
.Skip(position)
.Take(10)
.ToList();
可惜的是,儘管這項技術非常直覺,但也有一些嚴重缺點:
- 即使未傳回應用程式,資料庫仍必須處理前 20 個項目,這可能會建立很可觀的計算負載,隨著略過的列數而增加。
- 如果同時發生任何更新,您的分頁最終可能會略過特定項目或顯示該項目兩次。 舉例來說,如果某個項目在使用者從第 2 頁移至 3 時遭到移除,則整個結果集會「向上移位」,其中一個項目就會遭到略過。
索引鍵集分頁
針對位移式分頁,我們建議的替代方案,有時稱為索引鍵集分頁或搜尋式分頁,這種方法是單純使用 WHERE
子句來略過列,而不是使用位移。 這種方法就是說記住擷取的最後一個項目的相關值 (而不是位移),並要求取得該列之後的其他列。 舉例來說,假設我們擷取的最後一個頁面的最後一個項目有識別碼值 55,我們只需要執行下列動作:
var lastId = 55;
var nextPage = context.Posts
.OrderBy(b => b.PostId)
.Where(b => b.PostId > lastId)
.Take(10)
.ToList();
假設索引已在 PostId
定義,此查詢會非常有效率,且不會受到較低識別碼值發生的任何並行變更的影響。
索引鍵集分頁適用於使用者可前後瀏覽的分頁介面,但不支援使用者可以跳至任何特定頁面的隨機存取。 隨機存取分頁需要使用位移分頁,如上所述;由於位移分頁的缺點,請審慎考量您的使用案例是否確實需要隨機存取分頁,或使用下一頁/上一頁的瀏覽方式就已足夠。 如果需要隨機存取分頁,使用索引鍵集分頁來瀏覽下一頁/上一頁,並在跳至任何其他頁面時使用位移瀏覽,可能是穩固的實作方式。
多個分頁索引鍵
使用索引鍵集分頁時,經常需要依據多個屬性來排序。 舉例來說,下列查詢是依據日期和識別碼來分頁:
var lastDate = new DateTime(2020, 1, 1);
var lastId = 55;
var nextPage = context.Posts
.OrderBy(b => b.Date)
.ThenBy(b => b.PostId)
.Where(b => b.Date > lastDate || (b.Date == lastDate && b.PostId > lastId))
.Take(10)
.ToList();
這可確保下一頁會準確地從上一頁結束的位置接續。 加入更多排序索引鍵,就可以加入更多子句。
注意
大部分的 SQL 資料庫都支援上述作法更簡單且更有效率的版本,方法是使用列值:WHERE (Date, Id) > (@lastDate, @lastId)
。 EF Core 目前不支援在 LINQ 查詢表達此方法,我們會在 #26822 追蹤此問題。
索引數
和任何其他查詢一樣,適當的索引才能發揮良好的效能:請務必對照您的分頁順序編列索引。 如果排序依據有多個欄,您可以為這些欄定義一個索引,稱為複合式索引。
如需詳細資訊,請參閱索引的相關文件頁面。
其他資源
- 若要深入瞭解位移式分頁和索引鍵集分頁的缺點,請參閱這篇文章。
- .NET Data Community Standup 研討會則是我們討論分頁和示範上述所有概念的平台。
- 技術深入探討簡報則比較了位移和索引鍵集分頁。 雖然內容討論的是 PostgreSQL 資料庫,但其中的一般資訊也適用於其他關聯式資料庫。
- 如需在 EF Core 加上讓索引鍵集分頁簡化的擴充功能,請參閱 MR.EntityFrameworkCore.KeysetPagination 和 MR.AspNetCore.Pagination。