GridView 自定义分页的简洁解决方案






4.57/5 (35投票s)
本文展示了如何轻松扩展 GridView 以支持自定义分页,并消除了使用 ObjectDataSource 作为数据源的限制。
引言
ASP.NET 2.0 中的 GridView
控件在绑定到包含比页面大小更多项的数据源时,提供了对标准分页的强大支持。在具有小型数据集的简单应用程序中,这通常是一个足够的解决方案。然而,在您需要显示大约 10000 条记录的较大项目中,使用标准的内置分页功能拉取所有数据以绑定到 GridView
效率低下。我们需要自定义分页并动态处理数据块。查看 CodeProject、MSDN 和 Google 上可用的解决方案,我发现了两种建议。第一种建议是提供一个自定义寻呼器,您将在其中创建自己的页面导航控件,处理所有事件并处理显示。第二种建议强调在 ObjectDataSource
中使用 SelectCountMethod
返回虚拟项计数,GridView
将使用它来设置其分页。这种建议似乎足够和优雅,但如果您的数据源不是 ObjectDataSource
,它就无法工作。在我们当前的项目中,我们的数据源主要是通过业务服务层返回的 DataTable
或某些业务对象的 GenericContainer
(例如 List<T>
),因此 ObjectDataSource
解决方案不适用。许多人也遇到了同样的问题,别无选择,他们只好创建自己的寻呼器。这非常令人沮丧,如果您有过类似的经历,您将理解我的意思并真正欣赏本文。
我认为自定义分页的一个巧妙解决方案是允许像在 DataGrid
中那样设置 VirtualItemCount
属性,并且不限制用户只能使用 ObjectDataSource
,同时充分利用 GridView
中已内置的分页显示和交互。这激发了我编写 PagingGridView
控件和本文。
本文有意侧重于 GridView
的分页显示和交互方面,而不是数据块的检索。但是,为了完整性,我包含了使用 SQL Server 2005 的 ROW_NUMBER()
功能从 SQL Server 2005 检索页面行块的示例。
Using the Code
如果您将 PagingGridView
包含在 ASP.NET Web 应用程序项目或被 ASP.NET 网站项目引用的类库中,PagingGridView
组件将出现在您的工具箱中。要将其添加到您的页面,您可以像使用任何其他 Web 控件一样拖放它。
或者,如果您希望手动将代码添加到 ASCX/ASPX 文件中,首先您必须在文件开头注册标签,并包含由 TagPrefix
限定的 PagingGridView
控件元素。请参阅下面的示例
<%@ Register Assembly="PagingGridView"
Namespace="Fadrian.Web.Control" TagPrefix="cc1" %>
...
<cc1:PagingGridView ID="PagingGridView2" runat="server"/>
PagingGridView 代码解释
实现上述分页功能实际上非常简单。实现的关键在于 InitializePager
方法。PagingGridView
覆盖此方法并检查 CustomPaging
是否已打开。如果自定义分页已打开,那么我们需要调整一些设置,以便寻呼器将正确呈现数据源中的虚拟项以及当前页索引。
protected override void InitializePager(GridViewRow row,
int columnSpan, PagedDataSource pagedDataSource)
{
// This method is called to initialise the pager
// on the grid. We intercepted this and override
// the values of pagedDataSource to achieve
// the custom paging using the default pager supplied
if (CustomPaging)
{
pagedDataSource.AllowCustomPaging = true;
pagedDataSource.VirtualCount = VirtualItemCount;
pagedDataSource.CurrentPageIndex = CurrentPageIndex;
}
base.InitializePager(row, columnSpan, pagedDataSource);
}
PagingGridView
公开了一个公共属性 VirtualItemCount
。此属性的默认值为 -1,用户可以将其设置为任何整数值。如果此值设置为除 -1 以外的任何值,则 CustomPaging
属性将返回 true
以指示此控件已启用 CustomPaging
。
public int VirtualItemCount
{
get
{
if (ViewState["pgv_vitemcount"] == null)
ViewState["pgv_vitemcount"] = -1;
return Convert.ToInt32(ViewState["pgv_vitemcount"]);
}
set { ViewState["pgv_vitemcount"] = value; }
}
private bool CustomPaging
{
get { return (VirtualItemCount != -1); }
}
此控件中有一个内部属性 CurrentPageIndex
用于存储当前页索引。这里提出的问题是为什么我们不直接使用 PageIndex
?PageIndex
存储 GridView
的当前 PageIndex
,但在自定义分页场景中,每次我们绑定新的数据源(调用 DataBind
)时,如果数据源中的项数小于或等于 PageSize
,PageIndex
将重置为 0。在我们的情况下,我们只拉取页面块数据,数据源中的项数总是与 PageSize
相同,因此 PageIndex
将始终重置。我们通过引入 CurrentPageIndex
解决了这个问题,并且每次设置 DataSource
时,我们都会捕获该值并将其存储到 ViewState
。
private int CurrentPageIndex
{
get
{
if (ViewState["pgv_pageindex"] == null)
ViewState["pgv_pageindex"] = 0;
return Convert.ToInt32(ViewState["pgv_pageindex"]);
}
set { ViewState["pgv_pageindex"] = value; }
}
public override object DataSource
{
get { return base.DataSource; }
set
{
base.DataSource = value;
// we store the page index here so we dont lost it in databind
CurrentPageIndex = PageIndex;
}
}
数据源和分页数据
本文不打算深入探讨如何从数据库检索分页数据的详细信息;相反,它在此处提供信息是为了完整演示如何使用 PagingGridView
控件处理来自 SQL Server 2005 数据库的数据。为了使示例代码保持简单,所有查询都以代码形式编写,避免了 SQL 注入的风险或与使用存储过程相比的效率开销。
为了支持自定义分页,我们至少需要两件事:我们想要显示的总记录数(我们将其设置为 VirtualItemCount
)和用于显示特定页面项的数据。下面的代码展示了 GetRowCount
方法,它只是一个简单的 SELECT COUNT (*)
SQL 语句来检索行数。
GetDataPage
方法的实现是使用 SQL Server 2005 的 ROW_NUMBER()
功能,为要在网格上显示的特定页面检索特定的记录块。此方法中的 SQL 语句检索内部 Select
语句中按 ROW_NUM
排序的**顶部** x 条感兴趣的记录,而外部 Select
语句使用 WHERE
子句进一步过滤行。例如,如果我们要检索 PageIndex = 3
的记录块,并且 PageSize
设置为 20,则我们想要显示的记录块是第 61-80 行。使用相同的示例,当执行下面的代码中的 SQL 时,内部 Select
将检索“TOP 80
”行,然后外部 Select
通过“ROW_NUM > 60
”表达式过滤掉所有 <= 60 的行,以返回 20 条记录(第 61-80 行)。
有关使用 ROW_NUMBER()
的更多信息,请参阅 MSDN 或其他在线文章。
private const string demoConnString =
@"Integrated Security=SSPI;Persist Security Info=False;" +
@"Initial Catalog=NorthwindSQL;Data Source=localhost\SQLEXPRESS";
private const string demoTableName = "Customers";
private const string demoTableDefaultOrderBy = "CustomerID";
private int GetRowCount()
{
using (SqlConnection conn = new SqlConnection(demoConnString))
{
conn.Open();
SqlCommand comm = new SqlCommand(@"SELECT COUNT(*) FROM " + demoTableName, conn);
int count = Convert.ToInt32(comm.ExecuteScalar());
conn.Close();
return count;
}
}
private DataTable GetDataPage(int pageIndex, int pageSize, string sortExpression)
{
using (SqlConnection conn = new SqlConnection(demoConnString))
{
// We always need a default sort field for ROW_NUMBER() to work correctly
if (sortExpression.Trim().Length == 0)
sortExpression = demoTableDefaultOrderBy;
conn.Open();
string commandText = string.Format(
"SELECT * FROM (select TOP {0} ROW_NUMBER() OVER (ORDER BY {1}) as ROW_NUM, * "
+"FROM {2} ORDER BY ROW_NUM) innerSelect WHERE ROW_NUM > {3}",
((pageIndex + 1) * pageSize),
sortExpression,
demoTableName,
(pageIndex * pageSize));
SqlDataAdapter adapter = new SqlDataAdapter(commandText, conn);
DataTable dt = new DataTable();
adapter.Fill(dt);
conn.Close();
dt.Columns.Remove("ROW_NUM");
return dt;
}
}
如果您想知道本文的示例数据从何而来,我通过打开 Northwind Access 数据库并使用 Upsizing 向导在我的 SQL Server 2005 Express 中创建了一个新的数据库(包含完整架构和数据),从而创建了 NorthwindSQL 数据库。您可以重复此过程以重新创建数据来测试代码,或者简单地修改 demoConnString
、demoTableName
和 demoTableDefaultOrderBy
以反映您的数据存储。
实际应用
为了在您的目标页面中启用自定义分页,您必须在代码中设置 VirtualItemCount
或在 PagingGridView
控件的属性窗口中设置它。在下面的示例中,我们将 VirtualItemCount
设置为一个方法返回的值,该方法返回我们要检索的总记录的行数。
语法上,我们对 PagingGridView
的 DataSource
和 DataBind
进行编码,与 GridView
完全相同。您只需要清楚地记住,当我们分配 DataSource
时,无论是 DataTable
还是 GenericContainer
,数据集都应只包含该页面的数据项;否则,我们只是在浪费我们为启用 CustomPaging 所做的所有努力 :)
protected void Page_Load(object sender, EventArgs e)
{
if (!this.IsPostBack)
{
PagingGridView1.VirtualItemCount = GetRowCount();
BindPagingGrid();
}
}
protected void PagingGridView1_PageIndexChanging(object sender, GridViewPageEventArgs e)
{
PagingGridView1.PageIndex = e.NewPageIndex;
BindPagingGrid();
}
private void BindPagingGrid()
{
PagingGridView1.DataSource = GetDataPage(PagingGridView1.PageIndex,
PagingGridView1.PageSize, PagingGridView1.OrderBy);
PagingGridView1.DataBind();
}