使用 Repeater 和 SQL Server 2005 / 2008 实现高效分页
使用 Repeater 和 SQL Server 2005 中的 OVER() 关键字实现高效分页。
引言
这里提供的代码使用 ASP.NET Repeater
和 SQL Server 2005/2008(而非 2000)提供优化的分页解决方案。该实现不使用临时表、ROW_COUNT
或类似方法。它仅返回指定的行数,而不是完整数据集,以提高性能。该代码旨在非常简单,易于维护。
背景
分页在许多地方都有应用,但很难找到一种快速、高效且易于实现的优化解决方案。通过这篇文章,我尝试实现我认为高效的一些技术。欢迎所有评论和代码优化建议。
使用代码
要使用此代码,您必须拥有 SQL Server 2005、2008 或更高版本。Express 版本也可以工作。
- 首先运行 paging.sql 文件 - 它将创建一个表 [Employees],一个视图 [Paging_v] 和一个存储过程 [Paging]。 代码如下
- 现在将一些自定义数据加载到 [Employee] 表中 - 例如,添加 10-15 行用于测试。 我已经使用超过 1,000,000 行的数据测试了该代码,并且仍然高效。
- 以下代码是 paging.aspx 文件
- 以下是 paging.aspx.cs 文件 - 我正在使用 Microsoft Enterprise Library 从 SQL Server 获取数据,但您可以使用任何您喜欢的提供程序。
-- Create the table
CREATE TABLE dbo.Employees
(
EmployeeID int identity PRIMARY KEY,
FirstName nvarchar(64),
LastName nvarchar(64)
)
GO
-- Create the view
CREATE VIEW [dbo].[Paging_v]
AS
SELECT EmployeeID, FirstName, LastName
FROM dbo.Employees
GO
-- Create the stored procedure
CREATE PROCEDURE [dbo].[Paging]
(
@Page int,
@PageSize int
)
AS
SET NOCOUNT ON;
SELECT TOP(@PageSize) * FROM
(
SELECT RowID = ROW_NUMBER() OVER (ORDER BY EmployeeID),
EmployeeID, FirstName, LastName, TotalRows=Count(*) OVER()
FROM Paging_v
)
A WHERE A.RowID > ((@Page-1)*@PageSize)
SET NOCOUNT OFF
如您在存储过程中所见,我将 ROW_NUMBER() OVER
语句与 SQL TOP(@PageSize)
结合使用,以仅返回请求的行数。 与 #temp 表相比,这非常高效。
<%@ Page Language="C#" AutoEventWireup="true"
CodeFile="paging.aspx.cs"
Inherits="paging" EnableViewState="true" %>
<html>
<head>
<style type="text/css">
th {text-align:left;}
div.paging a {padding:0 3 0 3;}
</style>
</head>
<body>
<form runat="server">
<asp:ScriptManager runat="server" />
<asp:UpdatePanel ID="up1" runat="server">
<ContentTemplate>
<asp:Repeater ID="rep1" runat="server">
<HeaderTemplate>
<table style="width:100%">
<thead>
<tr><th>ID</th><th>First</th><th>Last</th></tr>
</thead>
<tbody>
</HeaderTemplate>
<ItemTemplate>
<tr>
<td><%# Eval("EmployeeID")%></td>
<td><%# Eval("FirstName") %></td>
<td><%# Eval("LastName") %></td>
</tr>
</ItemTemplate>
<FooterTemplate>
</tbody>
</table>
</FooterTemplate>
</asp:Repeater>
<br />
<div class="paging"><asp:PlaceHolder ID="plcPaging"
runat="server" /> <asp:HiddenField ID="hdCount"
runat="server" Value="0" /></div>
</ContentTemplate>
</asp:UpdatePanel>
</form>
</body>
</html>
我将 Repeater
封装在 UpdatePanel
中,但您可以根据需要删除它。有一个用于实际分页器的占位符,名为 plcPaging
,例如“1 2 3 4 5 6 ...”,以及一个隐藏字段,其中包含总行数。
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Text;
using Microsoft.Practices.EnterpriseLibrary.Data;
using System.Data.Common;
public partial class paging : System.Web.UI.Page
{
int pageSize = 15;
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
getData(1); // initially get data from the first page
createPaging();
}
else
{
plcPaging.Controls.Clear();
createPaging();
}
}
private void getData(int Page)
{
DataTable dt = PagingData(Page, pageSize);
if (dt.Rows.Count > 0)
{
hdCount.Value = dt.Rows[0]["TotalRows"].ToString();
rep1.DataSource = dt;
rep1.DataBind();
}
}
private DataTable PagingData(int Page, int PageSize)
{
// talk to database and return datatable
Database db = DatabaseFactory.CreateDatabase();
using (DbCommand cmd = db.GetStoredProcCommand("Paging"))
{
db.AddParameter(cmd, "@Page", DbType.Int32, 4,
ParameterDirection.Input, true, 10, 0, null, DataRowVersion.Default, Page);
db.AddParameter(cmd, "@PageSize", DbType.Int32, 4,
ParameterDirection.Input, true, 10, 0, null, DataRowVersion.Default, PageSize);
using (DataTable dt = (db.ExecuteDataSet(cmd)).Tables[0])
{
return dt;
}
}
}
protected void pager_Click(object sender, EventArgs e)
{
LinkButton lnk = sender as LinkButton;
int currentPage = int.Parse(lnk.Text);
getData(currentPage);
}
private void createPaging()
{
int rowCount = Convert.ToInt32(hdCount.Value.ToString());
if (rowCount <= pageSize)
// don't create the pager if there are less rows than specified pageSize.
return;
// e.g. 9 % 5 = 4 - means we have an extra page,
// so add 1 to rowCount otherwise add 0
rowCount = rowCount / pageSize + (rowCount % pageSize != 0 ? 1 : 0);
for (int i = 0; i < rowCount; i++)
{
LinkButton lnk = new LinkButton();
lnk.Click += new EventHandler(pager_Click);
lnk.ID = "p" + (i).ToString();
lnk.Text = (i+1).ToString();
plcPaging.Controls.Add(lnk);
}
}
}
解释
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
getData(1); // initially get data from the first page
createPaging();
}
else
{
plcPaging.Controls.Clear();
createPaging();
}
}
在 Page_Load
中,我们只是检查是否存在回发。 如果没有发生回发,我们使用 getData(1)
加载第一页数据。 调用 createPaging()
方法为数据集中的每个页面创建链接。
接下来,在 getData(int Page)
中,我们通过 PagingData(Page,pageSize)
方法从数据库获取数据。 如前所述,您可以使用任何提供程序,但我使用了 Enterprise Library 以提高清晰度。
private void getData(int Page)
{
DataTable dt = PagingData(Page, pageSize);
if (dt.Rows.Count > 0)
{
hdCount.Value = dt.Rows[0]["TotalRows"].ToString();
rep1.DataSource = dt;
rep1.DataBind();
}
}
private DataTable PagingData(int Page, int PageSize)
{
// talk to database and return datatable
Database db = DatabaseFactory.CreateDatabase();
using (DbCommand cmd = db.GetStoredProcCommand("Paging"))
{
db.AddParameter(cmd, "@Page", DbType.Int32, 4,
ParameterDirection.Input, true, 10, 0, null, DataRowVersion.Default, Page);
db.AddParameter(cmd, "@PageSize", DbType.Int32, 4,
ParameterDirection.Input, true, 10, 0, null, DataRowVersion.Default, PageSize);
using (DataTable dt = (db.ExecuteDataSet(cmd)).Tables[0])
{
return dt;
}
}
}
最后,我正在使用 EventHandler 来处理 plcPaging
控件中链接的单击事件。
protected void pager_Click(object sender, EventArgs e)
{
LinkButton lnk = sender as LinkButton;
int currentPage = int.Parse(lnk.Text);
getData(currentPage);
}
private void createPaging()
{
int rowCount = Convert.ToInt32(hdCount.Value.ToString());
if (rowCount <= pageSize)
// don't create the pager if there are less rows than specified pageSize.
return;
// e.g. 9 % 5 = 4 - means we have an extra page, so add 1 to rowCount otherwise add 0
rowCount = rowCount / pageSize + (rowCount % pageSize != 0 ? 1 : 0);
for (int i = 0; i < rowCount; i++)
{
LinkButton lnk = new LinkButton();
lnk.Click += new EventHandler(pager_Click);
lnk.ID = "p" + (i).ToString();
lnk.Text = (i+1).ToString();
plcPaging.Controls.Add(lnk);
}
}
分页仅生成数字,如“1 2 3 4 5 6” - 它不会生成“上一页”和“下一页”链接 - 欢迎提出任何反馈。
历史
- 2011/12/16 - 实现了版本 1。