[研究]ASP.NET,GridView 伺服器端分頁、資料庫分頁 (只讀取顯示於畫面的資料)
2025-05-02
環境:Visual Studio 2022 + ASP.NET + WebForm + Web Application + C# + SQL Server 2019 + SQL Server Management Studio (SSMS) 19
********************************************************************************
在 ASP.NET Web Forms 中,SqlDataSource 搭配 GridView 預設的分頁功能是「預先載入所有資料(全部)再進行記憶體分頁」,這種方式不適合大量資料。若你希望做到「按下一頁才向 SQL Server 查詢下一頁的 10 筆資料(資料庫分頁)」,你需要啟用「伺服器端分頁(Server-side paging)」。
範例
Default.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs"
Inherits="WebApplication1.Default" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
</head>
<body>
<form id="form1" runat="server">
<asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:MyDBConnectionString %>"
SelectCommand="SELECT * FROM (
SELECT
*,
ROW_NUMBER() OVER (ORDER BY [SN]) AS RowNum
FROM [MyTable]
) AS RowConstrainedResult
WHERE RowNum BETWEEN ((@PageIndex-1) * @PageSize + 1) AND (@PageIndex * @PageSize)
ORDER BY RowNum">
<SelectParameters>
<asp:ControlParameter Name="PageIndex" ControlID="TextBox_PageIndex" PropertyName="Text" Type="Int32" />
<asp:ControlParameter Name="PageSize" ControlID="TextBox_PageSize" PropertyName="Text" Type="Int32" />
</SelectParameters>
</asp:SqlDataSource>
<asp:GridView ID="GridView1" runat="server" AllowSorting="True"
AutoGenerateColumns="False" DataKeyNames="SN" DataSourceID="SqlDataSource1"
OnDataBound="GridView1_DataBound">
<Columns>
<asp:BoundField DataField="RowNum" HeaderText="##" SortExpression="RowNum" ReadOnly="True" />
<asp:BoundField DataField="SN" HeaderText="SN" SortExpression="SN" InsertVisible="False" ReadOnly="True" />
<asp:BoundField DataField="Field1" HeaderText="Field1" SortExpression="Field1" />
<asp:BoundField DataField="Field2" HeaderText="Field2" SortExpression="Field2" />
</Columns>
</asp:GridView>
共 <asp:Label ID="Label_RecordCount" runat="server" ForeColor="Blue"></asp:Label> 筆,
共 <asp:Label ID="Label_PageCount" runat="server" ForeColor="Blue"></asp:Label> 頁,
<asp:Button ID="Button_First" runat="server" Text="第一頁" OnClick="Button_First_Click" />
<asp:Button ID="Button_Prev" runat="server" Text="上一頁" OnClick="Button_Prev_Click" />
<asp:Button ID="Button_Next" runat="server" Text="下一頁" OnClick="Button_Next_Click" />
<asp:Button ID="Button_Last" runat="server" Text="最後一頁" OnClick="Button_Last_Click" />
第<asp:TextBox ID="TextBox_PageIndex" runat="server" Width="100px"></asp:TextBox>頁,
每頁<asp:TextBox ID="TextBox_PageSize" runat="server" Width="100px"></asp:TextBox>筆
<asp:Button ID="Button_Go" runat="server" Text="顯示" OnClick="Button_Go_Click" />
</form>
</body>
</html>
|
Default.aspx.cs
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.SqlClient;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace WebApplication1
{
public partial class Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
GridView1.VirtualItemCount = GetTotalRecordCount(); // 例如 45
TextBox_PageIndex.Text = "1";
TextBox_PageSize.Text = "10";
}
}
// 取得資料總筆數
private int GetTotalRecordCount()
{
using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["MyDBConnectionString"].ConnectionString))
{
SqlCommand cmd = new SqlCommand("SELECT COUNT(*) FROM [MyDB].[dbo].[MyTable]", conn);
conn.Open();
return (int)cmd.ExecuteScalar();
}
}
protected void GridView1_DataBound(object sender, EventArgs e)
{
Label_RecordCount.Text = GridView1.VirtualItemCount.ToString();
Int32.TryParse(TextBox_PageSize.Text, out int pageSize);
Label_PageCount.Text = ((GridView1.VirtualItemCount+ pageSize-1) / pageSize).ToString();
}
protected void Button_First_Click(object sender, EventArgs e)
{
TextBox_PageIndex.Text = "1";
GridView1.DataBind();
}
protected void Button_Prev_Click(object sender, EventArgs e)
{
int pageIndex = 0;
Int32.TryParse(TextBox_PageIndex.Text, out pageIndex);
if (pageIndex <= 1)
{
pageIndex = 1;
}
else
{
pageIndex = pageIndex - 1;
}
TextBox_PageIndex.Text = pageIndex.ToString();
GridView1.DataBind();
}
protected void Button_Next_Click(object sender, EventArgs e)
{
int pageIndex = 0;
int pageCount = 0;
Int32.TryParse(TextBox_PageIndex.Text, out pageIndex);
Int32.TryParse(Label_PageCount.Text, out pageCount);
if (pageIndex >= pageCount)
{
pageIndex = pageCount;
}
else
{
pageIndex = pageIndex + 1;
}
TextBox_PageIndex.Text = pageIndex.ToString();
GridView1.DataBind();
}
protected void Button_Last_Click(object sender, EventArgs e)
{
TextBox_PageIndex.Text = Label_PageCount.Text;
GridView1.DataBind();
}
protected void Button_Go_Click(object sender, EventArgs e)
{
GridView1.DataBind();
}
}
}
|
執行
********************************************************************************
記憶體分頁(In-Memory Paging)vs.伺服器端分頁(Server-side Paging)比較
| 項目 | 記憶體分頁(In-Memory Paging) | 伺服器端分頁(Server-side Paging) |
| 適用資料量 | 小量(< 500~1000 筆) | 大量(上千筆以上) |
| 初次載入速度 | 較慢(一次載入所有資料) | 較快(只載入目前頁的資料) |
| 換頁速度 | 快(資料已在記憶體) | 稍慢(每次換頁都需查詢資料庫) |
| 系統記憶體消耗 | 高(資料全在記憶體中) | 低(每次只存目前頁的資料) |
| 網路傳輸量 | 高(一次傳送全部資料) | 低(每次傳送一頁資料) |
| 對資料庫負擔 | 低(只查詢一次) | 中~高(每頁都查詢一次) |
| 開發簡單度 | 簡單(不需寫 SQL 分頁語法) | 較複雜(需搭配 OFFSET/FETCH 或 ROW_NUMBER) 需要 SQL Server 2012 或以上 |
| 不適合資料量大 | 是(易造成記憶體爆炸) | 否(專為大量資料設計) |
| 支援即時資料更新 | 差(資料已載入,不易反映更新) | 好(每頁都重新查詢) |
| 資料總筆數統計 | 自動已知 | 需額外查詢 COUNT(*) |
********************************************************************************
SqlDataSource + GridView 占用的記憶體何時釋放?
| 元件 | 記憶體釋放時機 | 說明 |
| SqlDataSource | ASP.NET 頁面生命週期結束後,由 GC 自動回收(前提是沒有手動綁定給全域變數、Session 等) | SqlDataSource 是伺服器控制項,綁定資料後資料會保留到 Page 完成處理 |
| GridView | 頁面結束後,整個控制項樹(包含 GridView.DataSource)都會被清除 | 若沒有保存 ViewState 的大型資料,就不會殘留記憶體 |
| GridView.DataSource | 若來自外部(如 DataTable),則會隨作用域結束+無其他參考被 GC 回收 | 除非被綁定在 Session、ViewState、Cache 等長期儲存區 |
********************************************************************************
2025-05-05 註:敝人實際測試了,「資料庫分頁」雖然降低了讀取的量,在後續 C# 和 ASP.NET 減少了時間和工作量;但卻增加了從 DB 中取得的時間,增加了 DB 的工作量,增加 C# 和 ASP.NET 的程式的複雜度和開發時時間,最好在 SSMS 中測試撈取的時間,看看看值不值得。
********************************************************************************
(完)
相關


沒有留言:
張貼留言