共用方式為


分頁

分頁是指擷取頁面的結果,而不是一次擷取全部結果;這種作法通常針對大量結果集執行,在顯示的使用者介面中,使用者可以瀏覽上一頁或下一頁的結果。

警告

不論使用何種分頁方法,請務必確保排序完全不重複。 舉例來說,如果結果的排序依據只有日期,但相同日期可能有多個結果,則結果可能會在分頁時遭到略過,因為這些結果在兩個分頁查詢中的排序不同。 同時依據日期和識別碼 (或任何其他唯一屬性或屬性組合) 排序,才能讓排序完全不重複並避免上述問題。 請注意,關聯式資料庫預設不會套用任何排序,對於主索引鍵也一樣。

注意

Azure Cosmos DB 有自己的分頁機制,請參閱專門文件頁面

位移分頁

使用資料庫實作分頁的常見方式是使用 SkipTake LINQ 運算子 (SQL 的 OFFSETLIMIT)。 假設頁面大小為 10 個結果,您可以使用 EF Core 擷取第三頁,如下所示:

var position = 20;
var nextPage = context.Posts
    .OrderBy(b => b.PostId)
    .Skip(position)
    .Take(10)
    .ToList();

可惜的是,儘管這項技術非常直覺,但也有一些嚴重缺點:

  1. 即使未傳回應用程式,資料庫仍必須處理前 20 個項目,這可能會建立很可觀的計算負載,隨著略過的列數而增加。
  2. 如果同時發生任何更新,您的分頁最終可能會略過特定項目或顯示該項目兩次。 舉例來說,如果某個項目在使用者從第 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 追蹤此問題。

索引數

和任何其他查詢一樣,適當的索引才能發揮良好的效能:請務必對照您的分頁順序編列索引。 如果排序依據有多個欄,您可以為這些欄定義一個索引,稱為複合式索引

如需詳細資訊,請參閱索引的相關文件頁面

其他資源