早期 ASP.NET 連接 Access (`.mdb`) 使用 `OleDb`,不支援 `OFFSET/FETCH`,需自行構造分頁 SQL。本文分析原始碼邏輯,指出效能與正確性問題,並提供 SQL Server 現代分頁寫法對比。
// 核心邏輯:利用 TOP + 子查詢排除前 N 筆
// 第 1 頁
SELECT TOP @PageSize * FROM Table WHERE [Condition] ORDER BY @OrderField @OrderDir
// 第 N 頁 (N>1)
SELECT TOP @PageSize * FROM Table
WHERE @OrderField > ALL (
SELECT TOP @Offset @OrderField FROM Table WHERE [Condition] ORDER BY @OrderField @OrderDir
) ORDER BY @OrderField @OrderDir
public DataTable GetPageAccess(string table, string[] fields, string where, string orderBy, int pageSize, int pageIndex)
{
// 1. 總筆數
string countSql = $"SELECT COUNT(*) FROM {table} WHERE {where}";
int total = (int)ExecuteScalar(countSql, GetParams(where));
// 2. 分頁查詢 (使用 ID 唯一鍵)
string fieldList = string.Join(",", fields);
string sql;
var parameters = GetParams(where); // 解析 where 中的參數
if (pageIndex == 1)
{
sql = $"SELECT TOP {pageSize} {fieldList} FROM {table} WHERE {where} ORDER BY {orderBy}";
}
else
{
int offset = (pageIndex - 1) * pageSize;
// 子查詢取得第 offset 筆的 ID
sql = $@"
SELECT TOP {pageSize} {fieldList} FROM {table}
WHERE {where} AND ID > (
SELECT MAX(ID) FROM (
SELECT TOP {offset} ID FROM {table} WHERE {where} ORDER BY {orderBy}
) AS T
) ORDER BY {orderBy}";
}
return ExecuteDataTable(sql, parameters);
}
string sql = @"
SELECT Id, Title, Content, CreateTime
FROM Articles
WHERE IsPublished = 1
ORDER BY CreateTime DESC
OFFSET @Offset ROWS FETCH NEXT @PageSize ROWS ONLY";
var parameters = new { Offset = (page - 1) * pageSize, PageSize = pageSize };
var items = conn.Query(sql, parameters).ToList();
int total = conn.ExecuteScalar("SELECT COUNT(*) FROM Articles WHERE IsPublished=1");
| 策略 | 適用場景 | 優點 | 缺點 |
|---|---|---|---|
| OFFSET/FETCH | 中小資料量、任意頁碼跳轉 | 標準語法、支援任意排序 | 深分頁 (第 10萬頁) 掃描前 N 行,慢 |
| 游標分頁 | 無限滾動、API 分頁、大資料 | 極快、穩定、不隨頁碼劣化 | 無法隨意跳頁、需唯一排序鍵 |
| 覆蓋索引 | 所有分頁查詢 | 避免回表、極大提升速度 | 寫入略慢、佔空間 |
| 快取總數 | 總數變化低頻 | 省去 COUNT(*) | 需快取失效機制 |
// 第一頁
SELECT TOP 20 * FROM Articles WHERE IsPublished=1 ORDER BY Id DESC
// 後續頁 (客戶端傳回上一頁最後一筆 ID)
SELECT TOP 20 * FROM Articles WHERE IsPublished=1 AND Id < @LastId ORDER BY Id DESC
public interface IPagedRepository
{
Task> GetPagedAsync(int page, int pageSize, Expression> filter,
Expression> orderBy, bool descending = true);
}
public class PagedResult
{
public List Items { get; set; }
public int TotalCount { get; set; }
public int Page { get; set; }
public int PageSize { get; set; }
public int TotalPages => (TotalCount + PageSize - 1) / PageSize;
}
手寫分頁核心是 **`COUNT(*)` + `TOP`/`OFFSET` 子查詢**。Access 受限於引擎,需自行構造子查詢並注意唯一鍵與參數化。SQL Server 2012+ 直接用 `OFFSET/FETCH`,大數據改用 **游標分頁**。現代開發**封裝 Repository 介面**,上層呼叫 `GetPagedAsync`,底層根據資料庫切換實作,業務邏輯完全隔離。