分頁是資料呈現的核心功能。本文提供兩種完整實作:LINQ to SQL 分頁 (適合 ORM 場景) 與 PagedDataSource 傳統分頁 (適合 DataTable/DataSet),並包含頁碼列生成邏輯,可直接套用於專案中。
public class LinqPager
{
private readonly MyDataContext _dc = new MyDataContext();
///
/// 通用 LINQ 分頁方法,回傳 HTML 頁碼列字串
///
public string GetPageHtml<T>(GridView gridView, int pageSize, IQueryable<T> query)
{
// 1. 取得當前頁碼 (QueryString)
int page = 1;
if (HttpContext.Current.Request.QueryString["page"] != null)
page = Convert.ToInt32(HttpContext.Current.Request.QueryString["page"]);
// 2. 總筆數 (執行 Count)
int totalCount = query.Count();
// 3. 分頁查詢 (Skip + Take) - 關鍵:延遲執行,只查當頁資料
var pageData = query.Skip((page - 1) * pageSize).Take(pageSize);
// 4. 繫結 GridView
gridView.DataSource = pageData;
gridView.DataBind();
// 5. 生成頁碼列 HTML
return GeneratePagerHtml(page, pageSize, totalCount);
}
///
/// 生成分頁 HTML (共用邏輯)
///
private string GeneratePagerHtml(int currentPage, int pageSize, int totalCount)
{
int totalPages = (int)Math.Ceiling((double)totalCount / pageSize);
if (totalPages == 0) totalPages = 1;
// 修正頁碼範圍
currentPage = Math.Max(1, Math.Min(currentPage, totalPages));
// 計算顯示頁碼範圍 (以當前頁為中心,最多顯示 10 頁)
int startPage = Math.Max(1, currentPage - 4);
int endPage = Math.Min(totalPages, startPage + 9);
if (endPage - startPage < 9) startPage = Math.Max(1, endPage - 9); // 確保滿 10 頁
var sb = new StringBuilder();
sb.Append($"共 {totalPages} 頁 / {totalCount} 筆 ");
// 首頁、上一頁
if (currentPage > 1)
{
sb.Append($"首頁 ");
sb.Append($"上一頁 ");
}
else
{
sb.Append("首頁 ");
sb.Append("上一頁 ");
}
// 數字頁碼
for (int i = startPage; i <= endPage; i++)
{
if (i == currentPage)
sb.Append($"{i} ");
else
sb.Append($"{i} ");
}
// 下一頁、末頁
if (currentPage < totalPages)
{
sb.Append($"下一頁 ");
sb.Append($"末頁");
}
else
{
sb.Append("下一頁 ");
sb.Append("末頁");
}
return sb.ToString();
}
}
public partial class ProductList : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
BindData();
}
}
private void BindData()
{
var pager = new LinqPager();
var query = from p in pager._dc.Products
where p.IsActive == true
orderby p.CreateDate descending
select p;
string pagerHtml = pager.GetPageHtml(gvProducts, 20, query);
litPager.Text = pagerHtml; // 頁面置放
}
}
public static string GetPageHtml(DataTable dt, DataList dataList, int pageSize)
{
// 1. 取得頁碼
int page = 1;
if (HttpContext.Current.Request.QueryString["page"] != null)
page = Convert.ToInt32(HttpContext.Current.Request.QueryString["page"]);
// 2. PagedDataSource 設定
var pds = new PagedDataSource
{
DataSource = dt.DefaultView,
AllowPaging = true,
PageSize = pageSize,
CurrentPageIndex = page - 1
};
// 3. 繫結
dataList.DataSource = pds;
dataList.DataBind();
// 4. 生成頁碼 (共用上述 GeneratePagerHtml 邏輯)
int totalCount = dt.Rows.Count;
return GeneratePagerHtml(page, pageSize, totalCount);
}
// GeneratePagerHtml 方法同上,略
[ToolboxData("<{0}:LinqPager runat=server>{0}:LinqPager>")]
public class LinqPagerControl : WebControl
{
[Bindable(true)]
public int PageSize { get; set; } = 20;
[Bindable(true)]
public string CssClass { get; set; } = "pagination";
public IQueryable DataSource { get; set; }
public GridView TargetGridView { get; set; }
protected override void Render(HtmlTextWriter writer)
{
if (DataSource == null || TargetGridView == null) return;
// 執行分頁邏輯 (同上)
// ...
// 渲染分頁 UI
writer.AddAttribute(HtmlTextWriterAttribute.Class, CssClass);
writer.RenderBeginTag(HtmlTextWriterTag.Nav);
writer.Write(pagerHtml);
writer.RenderEndTag();
}
}
| 場景 | 優化策略 |
|---|---|
| 大量資料 (10萬+) | 使用 ROW_NUMBER() OVER (ORDER BY ...) 視窗函數的 SQL 分頁,避免 Skip/Take 在資料庫端產生低效查詢計畫 |
| 頻繁查詢 | 加入索引於 `ORDER BY` 欄位;考慮快取總筆數 (Count) 短時間 (如 30 秒) |
| 複雜查詢 | 將 `Count()` 與分頁查詢分離,避免重複執行複雜 WHERE/JOIN |
| 前端體驗 | 使用 AJAX 部分更新 (UpdatePanel 或 jQuery AJAX) 避免整頁重載 |
// ASP.NET Core Controller
public async Task<IActionResult> Index(int page = 1, int pageSize = 20)
{
var query = _context.Products.Where(p => p.IsActive).OrderByDescending(p => p.CreateDate);
var totalCount = await query.CountAsync();
var items = await query.Skip((page - 1) * pageSize).Take(pageSize).ToListAsync();
ViewBag.Pager = new PagerViewModel(page, pageSize, totalCount);
return View(items);
}
// PagerViewModel 封裝頁碼生成邏輯
public class PagerViewModel
{
public int CurrentPage { get; }
public int TotalPages { get; }
public int TotalCount { get; }
// ... 生成 HTML 方法
}
LINQ 分頁核心在於 Skip() + Take() 的延遲執行特性,配合 `Count()` 取得總筆數。傳統 `PagedDataSource` 適合 DataTable 場景。關鍵在於:只查當頁資料、快取總筆數、頁碼 UI 與資料邏輯分離。新專案建議直接使用 EF Core + Pager ViewModel 模式。