ASP.NET GridView控件实现高效分页






4.93/5 (49投票s)
对于大规模可扩展的应用程序,分页必须是高效的。如果没有智能高效的分页和排序功能来处理海量数据,用户请求将花费更长的时间并消耗更多资源。
引言
我们所有在ASP.NET中使用GridView的开发者都知道,使用ASP.NET的GridView控件实现分页是一个简单的任务。对于数以万计的数据,如果我们想开发一个可扩展的应用程序,分页是一个重要的事情。
背景
在本演示中,我将开发一个ASP.NET应用程序。首先,我将展示通常使用GridView控件的分页。然后,我将增强分页功能,使其更有效。
开始吧
打开Microsoft SQL Server Management Studio 2005。按照以下结构设计一个表,或者您也可以使用不同的结构。
 
 
Profile表的一些记录。
 
 
在Visual Studio 2010中创建ASP.NET网站
- 创建一个网站项目,并将其命名为EffectivePagingAndSorting。
- 在该项目中添加一个Web窗体,并将页面重命名为NormalPaging.aspx。
在NormalPaging.aspx页面中添加一个GridView控件。将其重命名为gvProfile。在NormalPaging.aspx页面中添加一个SqlDataSource控件。将其重命名为profileDataSource。配置SqlDataSource控件,如下所示:
<asp:SqlDataSource ID="profileDataSource" runat="server" 
    ConnectionString = "<%$ ConnectionStrings:ApplicationServices %>" 
     SelectCommand="SELECT [ProfileId],[Name],[Address],[Email],[Mobile],
 Active = CASE [IsActive] WHEN 1 THEN 'Active' WHEN 0 THEN 'DeActive' 
    END FROM [dbo].[Profile]" >
您可能在web.config文件中使用了不同的连接字符串。在select命令SQL中,我使用了一个SQL CASE语句,以便从Profile表中显示“Active”(而不是1)和“DeActive”(而不是0)。
现在配置GridView控件以显示用户配置文件数据。将DataSourceID设置为profileDataSource,将AutoGenerateColumns设置为false(以便手动设置列),将AllowPaging设置为true(允许分页),将AllowSorting设置为true(允许通过单击列标题进行排序),将PageSize设置为5(以便grid一次只显示5条记录)。以及其他style属性,如下面的标记所示,或者根据您的需要进行设置。
 <asp:GridView ID="gvProfile" DataSourceID="profileDataSource" runat="server"
 AutoGenerateColumns="false" AllowPaging="true" AllowSorting="true" 
PageSize="5" HeaderStyle-Font-Names="Verdana" Font-Size="Small" 
HeaderStyle-HorizontalAlign="Left" 
HeaderStyle-Font-Underline="false" Width="55%" 
HeaderStyle-BackColor="BurlyWood" HeaderStyle-ForeColor="Navy">
            <alternatingrowstyle backcolor="</span>Aquamarine"> />
            <columns>
   <asp:BoundField  DataField="ProfileId" HeaderText="Profile Id" 
    SortExpression="ProfileId" ItemStyle-Width="6%"/>
   <asp:BoundField  DataField="Name" HeaderText="Name" 
    SortExpression="Name" ItemStyle-Width="13%"/>
   <asp:BoundField  DataField="Address" HeaderText="Address" 
    SortExpression="Address" ItemStyle-Width="18%"/>
   <asp:BoundField  DataField="Email" HeaderText="Email" 
    SortExpression="Email" ItemStyle-Width="8%"/>
   <asp:BoundField  DataField="Mobile" HeaderText="Mobile" 
    SortExpression="Mobile" ItemStyle-Width="9%"/>
   <asp:BoundField  DataField="IsActive" HeaderText="Status" 
    SortExpression="IsActive" ItemStyle-Width="4%"/>    
在上面的标记中,网格列已手动绑定到数据源。
运行应用程序浏览NormalPaging.aspx。您将在Web浏览器中看到如下所示的结果:
 
 
工作原理
为了在NormalPaging.aspx页面上使用GridView显示这些记录,我为SqldataSource编写了select命令,然后将SqlDataSource指定为GridView控件的数据源。实际上,对于NormalPaging.aspx页面的每一次加载,都会从底层Profile表中检索所有记录到内存中。然后Gridview按页显示这些记录。设想一下,如果底层表有数百万条记录会发生什么。您肯定会同意,当页面加载时,它会检索所有数百万条记录。因此,加载时间会很长。并且加载完成后,它必须占用大量的系统资源。因此,这种分页方式会降低应用程序的可扩展性。所以,对于成千上万条记录,这种分页方式并不有效。
高效分页
为了使分页和排序有效,第一步是在SQL中创建存储过程。
- 打开 SQL Server Management Studio
- 新建查询
在您的数据库中创建一个具有某种技术的存储过程,以从Profile表中检索记录。例如,如下SQL:
CREATE PROCEDURE [dbo].[Profile_GET]
    @PageSize int = null,
    @CurrentPage int = null,
    @SortExpression    nvarchar(max) = null
AS
BEGIN
    SET NOCOUNT ON
    DECLARE @SqlString nvarchar(max)
    Declare @UpperBand int
    Declare @LowerBand int        
    
    SET @LowerBand  = (@CurrentPage - 1) * @PageSize
    SET @UpperBand  = (@CurrentPage * @PageSize) + 1    
    BEGIN
        SET @SqlString='WITH tempProfile AS
        (                    
            SELECT 
                [ProfileId],
                [Name],
                [Address],
                [Email],
                [Mobile], 
                [Active] = CASE [IsActive] WHEN 1 _
                THEN ''Active'' WHEN 0 THEN ''DeActive'' END,                            
                ROW_NUMBER() OVER (ORDER BY '+ _
                @SortExpression +' ) AS RowNumber                 
                FROM [dbo].[Profile]
        )     
        SELECT 
            [ProfileId],
            [Name],
            [Address],
            [Email],
            [Mobile], 
            [Active]                                        
        FROM 
            tempProfile 
        WHERE 
            RowNumber > ' + CONVERT(VARCHAR,@LowerBand) + _
            '  AND RowNumber < ' + CONVERT(VARCHAR, @UpperBand)
            + ' ORDER BY ' + @SortExpression            
    
    EXEC sp_executesql @SqlString
    END
END
Profile_GET存储过程接受PageSize、CurrentPage和SortExpression作为输入参数。PageSize - 是GridView一次显示一页的记录数。CurrentPage – 您当前在GridView中的页面。SortExpression – 按哪个字段对一页的记录进行排序。
所有这些参数都将在应用程序运行时由GridView控件从Web页面传递。
在过程中,设置SET NOCOUNT ON,这样查询就不会产生像下面这样的消息,这些消息会增加查询的输出量。
(29 row(s) affected)
SET @LowerBand  = (@CurrentPage - 1) * @PageSize
SET @UpperBand  = (@CurrentPage * @PageSize) + 1
以上两个方程将计算一页的上限和下限。下限是指一页的起始行位置,上限是指一页的顶部行位置。假设您当前在第5页,页面大小为5。那么以上两个方程的结果将是:
LowerBand = (5 -1) * 5 = 20 
UpperBand = (5 * 5) + 1 = 26
因此,这些方程将返回位置编号为21-25的记录(因为我在后面的过程中在where条件中使用了大于和小于)。
WITH tempProfile AS
    (                    
        SELECT 
            [ProfileId],
            [Name],
            [Address],
            [Email],
            [Mobile], 
        [Active] = CASE [IsActive] WHEN 1 THEN 'Active' _
            WHEN 0 THEN 'DeActive' END,                            
            ROW_NUMBER() OVER (ORDER BY '+ _
            @SortExpression +' ) AS RowNumber                 
            FROM [dbo].[Profile]
    )  
WITH SQL语句后跟表名,将从其内部的SELECT SQL语句生成一个临时表。
ROW_NUMBER() OVER (ORDER BY '+ @SortExpression +' ) AS RowNumber        
上面的SQL语句将向临时表tempProfile添加一个名为RowNumber的附加列,并在对临时表中的每个记录按排序表达式参数升序排序后为其分配一个顺序编号。这个RowNumber列将用于后续的分页。
 
 
临时tempProfile表中的一些记录,其中记录已按Profile Id排序。
现在根据PageSize、CurrentPage和SortExpresssion参数的请求过滤记录。
SELECT 
[ProfileId],
     [Name],
 [Address],
     [Email],
       [Mobile], 
       [Active]                                        
       FROM 
         tempProfile 
        WHERE 
            RowNumber > ' + CONVERT(VARCHAR,@LowerBand) + '  _
		AND RowNumber < ' + CONVERT(VARCHAR, @UpperBand)
            + ' ORDER BY ' + @SortExpression      
我通过EXEC sp_executesql执行了动态SQL,因为SortExpression是通过参数传递的。
EXEC sp_executesql @SqlString
现在,您需要创建另一个存储过程。
CREATE PROCEDURE [dbo].[Profile_Total]
AS
BEGIN
    SET NOCOUNT ON
    SELECT COUNT(*) FROM Profile
END
上面的Profile_Total将返回记录的总数。
现在回到Visual Studio。添加一个类,我创建了ProfileDataSource.cs。此类型将由对象数据源使用。因此,我们需要使其成为对象数据源的数据源。
[DataObject(true)]
public class ProfileDataSource
{
    public ProfileDataSource()
    {
    }
    [DataObjectMethod(DataObjectMethodType.Select, false)]
    public Int32 TotalRowCount(Int32 startRowIndex, 
    	Int32 pageSize, String sortExpression)
    {
        Int32 intTotalProfile = 0;
        using (SqlConnection conn = new SqlConnection
        (ConfigurationManager.ConnectionStrings["ApplicationServices"].ToString()))
        {
            SqlCommand cmdSelect = new SqlCommand();
            conn.Open();
            cmdSelect.CommandText = "Profile_Total";
            cmdSelect.CommandType = CommandType.StoredProcedure;
            cmdSelect.Connection = conn;
            SqlDataReader dataReader = cmdSelect.ExecuteReader();
            dataReader.Read();
            intTotalProfile = Convert.ToInt32(dataReader[0]);
        }
        return intTotalProfile;
    }
    [DataObjectMethod(DataObjectMethodType.Select, true)]
    public static DataTable GetProfileData
    (Int32 startRowIndex, Int32 pageSize, String sortExpression)
    {
        DataTable profileDataTable = new DataTable();
        using (SqlConnection conn = new SqlConnection
        (ConfigurationManager.ConnectionStrings["ApplicationServices"].ToString()))
        {
            SqlCommand cmdSelect = new SqlCommand();
            conn.Open();
            cmdSelect.CommandText = "Profile_GET";
            cmdSelect.CommandType = CommandType.StoredProcedure;
            cmdSelect.Connection = conn;                
            startRowIndex = Convert.ToInt32(startRowIndex / pageSize) + 1;
            
            if (String.IsNullOrEmpty(sortExpression))
                sortExpression = "ProfileId";
            cmdSelect.Parameters.AddWithValue("@CurrentPage", startRowIndex);
            cmdSelect.Parameters.AddWithValue("@PageSize", pageSize);
            cmdSelect.Parameters.AddWithValue("@SortExpression", sortExpression);
            SqlDataAdapter dataAdapter = new SqlDataAdapter();
            dataAdapter.SelectCommand = cmdSelect;
            dataAdapter.Fill(profileDataTable);                
        }
        return profileDataTable;
    }
}
添加以下命名空间
using System.Configuration;
using System.Data.SqlClient;
using System.ComponentModel;
现在通过添加DataObject属性将ProfileDataSource类型标记为数据对象。
 [DataObject(true)]
 public class ProfileDataSource
 {
  }
在该类型中添加两个方法。TotalRowCount用于获取底层表中的总记录数。GetProfileData用于从底层表中获取分页记录。
现在为数据对象Select方法编写方法。
[DataObjectMethod(DataObjectMethodType.Select, false)]
public Int32 TotalRowCount(Int32 startRowIndex, Int32 pageSize, String sortExpression)
{
}
[DataObjectMethod(DataObjectMethodType.Select, true)]
public static DataTable GetProfileData(Int32 startRowIndex, 
	Int32 pageSize, String sortExpression)
{
}
为TotalRowCount方法编写正文。此方法的参数稍后将由对象数据源控件传递。它将执行我们已经创建的Ptofile_Total存储过程,并将总记录数返回给对象数据源控件。
[DataObjectMethod(DataObjectMethodType.Select, false)]
 public Int32 TotalRowCount(Int32 startRowIndex, Int32 pageSize, String sortExpression)
 {
    Int32 intTotalProfile = 0;
    using (SqlConnection conn = new SqlConnection
	(ConfigurationManager.ConnectionStrings["ApplicationServices"].ToString()))
    {
         SqlCommand cmdSelect = new SqlCommand();
         conn.Open();
         cmdSelect.CommandText = "Profile_Total";
         cmdSelect.CommandType = CommandType.StoredProcedure;
         cmdSelect.Connection = conn;
         SqlDataReader dataReader = cmdSelect.ExecuteReader();
         dataReader.Read();
         intTotalProfile = Convert.ToInt32(dataReader[0]);
    }
    return intTotalProfile;
}
为GetProfileData方法编写正文。此方法的参数将由对象数据源控件传递。它将执行我们已经创建的Profile_GET存储过程,并带参数以在每次请求时获取分页记录。最后,此方法将返回一个数据表给对象数据源控件。
[DataObjectMethod(DataObjectMethodType.Select, true)]
public static DataTable GetProfileData(Int32 startRowIndex, 
	Int32 pageSize, String sortExpression)
{
DataTable profileDataTable = new DataTable();
using (SqlConnection conn = new SqlConnection
	(ConfigurationManager.ConnectionStrings["ApplicationServices"].ToString()))
{
SqlCommand cmdSelect = new SqlCommand();
conn.Open();
cmdSelect.CommandText = "Profile_GET";
cmdSelect.CommandType = CommandType.StoredProcedure;
cmdSelect.Connection = conn;                
              startRowIndex = Convert.ToInt32(startRowIndex / pageSize) + 1;
              if (String.IsNullOrEmpty(sortExpression))
                 sortExpression = "ProfileId";
              cmdSelect.Parameters.AddWithValue("@CurrentPage", startRowIndex);
              cmdSelect.Parameters.AddWithValue("@PageSize", pageSize);
              cmdSelect.Parameters.AddWithValue("@SortExpression", sortExpression);
              SqlDataAdapter dataAdapter = new SqlDataAdapter();
              dataAdapter.SelectCommand = cmdSelect;
dataAdapter.Fill(profileDataTable);                
            }
            return profileDataTable;
        }
   startRowIndex = Convert.ToInt32(startRowIndex / pageSize) + 1;
这行代码根据startRowIndex和pageSize参数计算开始行索引。
当页面首次加载时,对象数据源控件会将sortExpression参数传递为null。在这种情况下,sort表达式将是ProfileId,这是底层表的主键。
if (String.IsNullOrEmpty(sortExpression))
sortExpression = "ProfileId";
EffectivePaging.aspx
将NormalPaging.aspx中的GridView标记复制到EffectivePaging.aspx。
<asp:GridView ID="gvProfile" DataSourceID="profileDataSource" runat="server" 
AutoGenerateColumns="false" AllowPaging="true" AllowSorting="true" PageSize="5" 
HeaderStyle-Font-Names="Verdana" Font-Size="Small" 
HeaderStyle-HorizontalAlign="Left" HeaderStyle-Font-Underline="false" 
	Width="55%" HeaderStyle-BackColor="BurlyWood" 
HeaderStyle-ForeColor="Navy">
            <alternatingrowstyle backcolor="</span>Aquamarine"> />
            <columns>
                <asp:BoundField  DataField="ProfileId" HeaderText="Profile Id" 
SortExpression="ProfileId" ItemStyle-Width="6%"/>
                <asp:BoundField  DataField="Name" HeaderText="Name" 
SortExpression="Name" ItemStyle-Width="13%"/>
                <asp:BoundField  DataField="Address" HeaderText="Address" 
SortExpression="Address" ItemStyle-Width="18%"/>
                <asp:BoundField  DataField="Email" HeaderText="Email" 
SortExpression="Email" ItemStyle-Width="8%"/>
                <asp:BoundField  DataField="Mobile" HeaderText="Mobile" 
SortExpression="Mobile" ItemStyle-Width="9%"/>
                <asp:BoundField  DataField="IsActive" HeaderText="Status" 
SortExpression="IsActive" ItemStyle-Width="4%"/>          
在Effectivepaging.aspx页面中添加一个对象数据源控件。我已经创建了profileDataSource。将其设置为gvProfile数据源ID。
DataSourceID="profileDataSource"
<asp:ObjectDataSource ID="profileDataSource" runat="server" 
	SelectMethod="GetProfileData" EnablePaging="true"
MaximumRowsParameterName="pageSize"
        StartRowIndexParameterName="startRowIndex" 
	TypeName="VTS.Web.UI.ProfileDataSource"  SelectCountMethod="TotalRowCount" 
        SortParameterName="sortExpression">
        <SelectParameters>
            <asp:Parameter Name="startRowIndex" Type="Int32" />
            <asp:Parameter Name="pageSize" Type="Int32"/>
            <asp:Parameter Name="sortExpression" Type="String" />            
        </SelectParameters>
设置一些属性:将EnablePaging设置为true以启用分页,
将MaximumRowsParameterName设置为pageSize以指定当前页面大小,
将StartRowIndexParameterName设置为startRowIndex以指定开始行号位置,
将TypeName设置为VTS.Web.UI.ProfileDataSource(用于具有命名空间的Data对象),
将SelectMethod设置为GetProfileData以获取分页记录,
将SelectCountMethod设置为TotalRowCount以获取底层表的总记录数,这将用于分页,
将SortParameterName设置为sortExpression以进行排序。
对象数据源的Select参数
<SelectParameters>
<asp:Parameter Name="startRowIndex" Type="Int32" />
<asp:Parameter Name="pageSize" Type="Int32"/>
<asp:Parameter Name="sortExpression" Type="String" />            
</SelectParameters>
现在运行应用程序以在浏览器中浏览EffectivePaging.aspx页面。您将看到如下所示的结果:
 
 
工作原理
在SQL Management Studio的“工具”菜单下打开SQL Profile,以诊断高效分页的幕后工作原理。- 从“文件”菜单打开新跟踪
- 使用您的凭据登录
- 选择“运行”
- 在“编辑”菜单下单击“清除跟踪窗口”以清除现有跟踪。
- 最小化SQL Server Profiler
- 从Visual Studio运行应用程序以浏览EffectivePaging.aspx页面
首次运行应用程序后,您将获得如下所示的跟踪:
 
 
从Profiler复制跟踪,并在SQL Management Studio中使用SQL数据库运行它。
exec Profile_GET @CurrentPage=1,@PageSize=5,@SortExpression=N'ProfileId'
您将获得以下输出:
 
 
现在清除SQL Profiler中已创建的跟踪并将其最小化。
单击当前正在运行的EffectivePaging.aspx页面上的第2页。返回到SQL Profiler,获取第2页的跟踪。
exec Profile_GET @CurrentPage=2,@PageSize=5,@SortExpression=N'ProfileId'
在SQL Management Studio中使用SQL数据库运行它。您将得到如下结果:
 
 
对GridView底部的所有可用页面继续执行这些步骤,还可以通过单击每个列的标题来排序。每次请求都会获得5条记录。因此,不会出现加载底层表中所有记录的情况。只加载您在GridVew的pageSize属性中设置的记录数。记录加载速度比以前的普通分页快。它将占用更少的系统资源。对于成千上万条数据来说,这是非常有效的。
结论
对于处理大量记录以构建可扩展应用程序的应用程序来说,高效分页非常重要。本演示将帮助您创建可扩展的应用程序。


