ASP.NET 中的自定义数据分页






3.70/5 (10投票s)
2003年5月30日
4分钟阅读

159474

2356
如何使用 Repeater 控件和一些 T-SQL 编程来实现数据分页。
引言
尽管 DataGrid
控件通过启用 AllowPaging
属性为 DataSource
的记录提供了内置的分页支持,但微软发现,以这种方式分页存在一个很大的缺点:当数据库中有成千上万条记录或更多时,每次导航到新页面时,这些记录都必须从数据源检索到内存中。这会严重降低性能。因此,微软允许我们通过启用 AllowCustomPaging
来实现自定义分页解决方案,以绕过此限制。现在,您只需检索当前页面所需的记录,而不是检索所有记录来显示每一页。
这听起来像是我们需要的,但我发现以这种方式自定义数据分页也有其缺点,因为它只适用于具有标识列且标识列没有缺失值的表。如果某些值缺失,DataGrid
将会显示某些页面的记录少于其他页面。例如,DataGrid
准备显示 10 条唯一 ID 从 10 到 20 的记录,但由于某些原因,最后 5 条记录丢失了。您会期望 DataGrid
显示从 10 到 25 的十条记录,但实际上它不会。它只会显示从 10 到 20 的五条记录,而这并不是您想要的。
因此,为了解决这个问题,我将在这里演示如何通过使用 Repeater
和 T-SQL 编程来实现我们自己的自定义分页解决方案,这需要多做一些工作。我们可以改用 DataList
或 DataGrid
,但 Repeater
控件是最轻量级的,而且我们不需要 DataList
和 DataGrid
控件提供的任何特殊功能。
要理解本文,我们只需要创建一个数据库和一个网页。让我们从创建数据库开始。
创建数据库
我们将创建一个名为 CustomPaging
的数据库,一个名为 Products
的表,然后添加一个名为 GetProductsByPage
的存储过程来检索特定页面的产品。
CREATE DATABASE CustomPaging
GO
Use CustomPaging
GO
CREATE TABLE Products (
ProductID int IDENTITY (1, 1) NOT NULL ,
ProductName varchar (50) NOT NULL
) ON [PRIMARY]
GO
CREATE PROCEDURE GetProductsByPage
@PageNumber int,
@PageSize int
AS
CREATE TABLE #TempProducts
(
ID int IDENTITY PRIMARY KEY,
ProductID int,
ProductName varchar(50),
)
-- fill the temp table with all the products for the
-- specified products retrieved from the Products table
INSERT INTO #TempProducts
(
ProductID,
ProductName
)
SELECT
ProductID,
ProductName
FROM Products
-- declare two variables to calculate the
-- range of records to extract for the specified page
DECLARE @FromID int
DECLARE @ToID int
-- calculate the first and last ID of the range of topics we need
SET @FromID = ((@PageNumber - 1) * @PageSize) + 1
SET @ToID = @PageNumber * @PageSize
-- select the page of records
SELECT ProductID, ProductName FROM #TempProducts
WHERE ID >= @FromID AND ID <= @ToID
GO
在代码中创建数据库和表非常简单,只有创建存储过程 GetProductsByPage
的代码比较长,如果您不熟悉 T-SQL,可能难以理解。它的作用是:首先,它创建一个临时表,该表包含与 Products
表匹配的所有列,以及一个新的标识列。然后,它将 Products
表中的所有记录插入到 temp
表中,这样 temp
表的新标识列就没有缺失值了。接下来,我们声明两个变量来计算请求页面记录的范围。
因此,通过使用此存储过程,我们只需要传递两个参数 PageNumber
和 PageSize
(每页记录数)即可检索需要为页面显示的记录。现在,我们将这些记录绑定到 Repeater
控件。
将数据绑定到 Repeater 控件
接下来,我们将把数据绑定到 Repeater
控件。因此,我们需要在我们的 web 窗体中声明一个 Repeater
控件。以下 HTML 代码添加了一个 Repeater
控件和一些用于导航页面的按钮。
<script language="javascript">
function ChangePage(id)
{
// save the page number clicked to the hidden field
document.all.PageNumber.value = id;
// call the __doPostBack function to post back
// the form and execute the PageClick event
__doPostBack('PageClick','');
}
</script>
<body bgcolor="black">
<form id="Topics" method="post" runat="server">
<!-- Hidden fields store the current page number
and total pages -->
<input type="hidden" runat="server"
id="PageNumber" value="1">
<input type="hidden" runat="server"
id="Pages" value="0">
<!-- Hidden button to handle the click event when
user clicks on a page link-->
<asp:button ID="PageClick" OnClick="Page_Click"
runat="server" Visible="false"></asp:button>
<asp:label ID="Info" runat="server"></asp:label>
<asp:linkbutton ID="FirstPage"
runat="server" CommandName="FirstPage"
OnCommand="Page_Changed">First</asp:linkbutton>
<asp:linkbutton ID="PrevPage" runat="server"
CommandName="PrevPage"
OnCommand="Page_Changed">Prev</asp:linkbutton>
<asp:label ID="PagesDisplay"
runat="server"></asp:label>
<asp:linkbutton ID="NextPage"
runat="server" CommandName="NextPage"
OnCommand="Page_Changed">Next</asp:linkbutton>
<asp:linkbutton ID="LastPage" runat="server"
CommandName="LastPage"
OnCommand="Page_Changed">Last</asp:linkbutton>
<br><br>
<table width="300" style="border: 1 solid gray" align="center">
<tr>
<td bgcolor="gray"
style="color: white">Product ID</td>
<td bgcolor="gray"
style="color: white">Product Name</td>
</tr>
<asp:repeater ID="ProductsRepeater" runat="server">
<itemtemplate>
<tr>
<td><%# DataBinder.Eval(Container.DataItem,
"ProductID") %></td>
<td><%# DataBinder.Eval(Container.DataItem,
"ProductName") %></td>
</tr>
</itemtemplate>
</asp:repeater>
</table>
</form>
</body>
我们有两个隐藏字段用于存储当前页码和总页数。然后我们有四个导航按钮:首页、上一页、下一页、末页,它们共享相同的命令,但具有唯一的命令名称。这意味着当这些按钮中的一个被点击时,事件处理程序 Paged_Changed
将被执行。我们还有一个 Label
用于将所有页面显示为链接,这有助于用户更轻松地跳转到其他页面。最后,我们有一个 Repeater
用于显示记录。
请注意,有一个 JavaScript 函数 ChangePage()
,当用户单击页码时会调用该函数,将该页码保存到 PageNumber
隐藏字段,然后调用 __doPostBack
函数(由 ASP.NET 自动生成),以提交表单并执行 Page_Click
事件处理程序来刷新页面并显示新记录。
HTML 代码就到这里。现在让我们看一下下面的 C# 代码,了解如何将数据绑定到 Repeater
并实现分页导航器。
<%@ Page Language="C#" ContentType="text/html"
ResponseEncoding="iso-8859-1" %>
<%@ import Namespace="System" %>
<%@ import Namespace="System.Data" %>
<%@ import Namespace="System.Data.SqlClient" %>
<script runat="server">
const int RECORDS_PER_PAGE = 5;
int totalRecords;
void Page_Load()
{
// get the number of records found in the database
totalRecords = GetProductsCount();
// calculate and save the number of
// pages to the Pages hidden field
Pages.Value = _
Math.Ceiling((double)totalRecords/RECORDS_PER_PAGE).ToString();
if (!Page.IsPostBack)
{
// Bind records to repeater
BindData();
}
}
void BindData()
{
int pageNumber = int.Parse(PageNumber.Value);
int totalPages = int.Parse(Pages.Value);
SqlConnection connection = new
SqlConnection("server=(local);database=CustomPaging;uid=sa;pwd=;");
SqlCommand command = new SqlCommand("GetProductsByPage", connection);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add("@PageNumber", pageNumber);
command.Parameters.Add("@PageSize", RECORDS_PER_PAGE);
connection.Open();
DataSet products = new DataSet();
SqlDataAdapter adapter = new SqlDataAdapter();
adapter.SelectCommand = command;
adapter.Fill(products, "Products");
connection.Close();
// Bind Data to the repeater
ProductsRepeater.DataSource = products;
ProductsRepeater.DataBind();
// Display the page links
PagesDisplay.Text = "";
for (int i=1; i<=totalPages; i++)
{
if (pageNumber != i)
PagesDisplay.Text += _
"<a href=\"javascript:ChangePage("+i+")\">"+i+"</a> ";
else
PagesDisplay.Text += "[" + i + "] ";
}
// enable/disable the links to navigate through the pages
FirstPage.Enabled = (pageNumber != 1);
PrevPage.Enabled = (pageNumber != 1);
NextPage.Enabled = (pageNumber != totalPages);
LastPage.Enabled = (pageNumber != totalPages);
Info.Text = totalRecords + " records are found and divided into "
+ Pages.Value + " pages<br><br>";
}
// return the number of total records in database
int GetProductsCount()
{
SqlConnection connection = new
SqlConnection("server=(local);database=CustomPaging;uid=sa;pwd=;");
SqlCommand command = new
SqlCommand("SELECT Count(*) FROM Products", connection);
connection.Open();
int count = (int)command.ExecuteScalar();
connection.Close();
return count;
}
// execute when user clicks on the next/prev/first/last button
void Page_Changed(object sender, CommandEventArgs e)
{
switch (e.CommandName)
{
case "FirstPage":
PageNumber.Value = "1";
break;
case "PrevPage":
PageNumber.Value = _
(int.Parse(PageNumber.Value) -1).ToString();
break;
case "NextPage":
PageNumber.Value = _
(int.Parse(PageNumber.Value) +1).ToString();
break;
case "LastPage":
PageNumber.Value = Pages.Value;
break;
}
// rebind data
BindData();
}
// execute when user clicks on the page link
void Page_Click(object sender, System.EventArgs e)
{
// rebind data
BindData();
}
</script>
当页面加载时,它通过调用 GetProductsCount()
方法获取总记录数,然后计算页数并将其保存到表单的隐藏字段中。接下来,它调用 BindData()
方法将数据绑定到 Repeater
控件,并在页面未回发时显示页面链接,因为回发时不需要重新绑定数据。
上面的代码有两个事件处理程序,第一个是 Page_Click
,它只重新绑定数据到 Repeater
;另一个是 Page_Changed
,当用户单击 4 个导航按钮中的一个时执行。它通过命令名称检测哪个按钮被点击,并将要显示的页码保存到隐藏字段,然后重新绑定数据。
这就是实现我们自定义数据分页所需的所有内容。感谢您的阅读。