使用 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。




