使用Gridview和ObjectDataSource轻松实现自定义分页

  一.什么是自定义分页

        自定义分页是与默认分页相对应的。默认分页指一次检索出所有数据并将其绑定到数据绑定控件中,虽然该控件只能一页一页显示这些数据,但是所有数据其实都已经被绑定到控件上了。自定义分页的含义是显示到哪一页就检索并绑定哪一页的数据。显然在大数据量的情况下,自定义分页的效率会高很多。  
        在Asp.net 1.x中自定义分页又称数据库分页,DataGrid中的AllowCustomerPaging属性和VirtualItemCount属性就是专门为自定义分页准备的。在Asp.net 2.0中因为引入了数据源的概念,因此自定义分页也可以叫做数据源分页。

二.为什么使用ObjectDataSource

        ASP.NET2.0提供了SqlDataSource数据源控件提供了ConnectionStringSelectCommandSelectCommandTypeSelectParameters等属性,分别用于指定连接字符串、SQL查询语句、SQL查询语句的类型和查询所使用的参数。SqlDataSource数据源控件根据这些属性的设定从关系数据库中获取数据。但是,SqlDataSource 控件存在一个问题:该控件的缺点在于它迫使您将用户界面层与数据访问层混合在一起,忽略了业务逻辑层。然而随着应用程序规模的扩大,具有清晰的用户界面层、业务逻辑层、数据访问层以及数据实体层是极为必要的。仅仅通过 SqlDataSource 控件的属性,在用户界面层引用 SQL 语句或存储过程是不可取的,或说是缺乏架构意识的,不利于代码的重用和维护。另外,SqlDataSource也不支持数据源分页,也就不能实现自定义分页。
        ObjectDataSource 控件对象模型类似于 SqlDataSource 控件。但ObjectDataSource 提供一个 TypeName 属性(而不是 ConnectionString属性),该属性指定用于执行数据操作的业务逻辑类的类名,ObjectDataSource可以通过TypeName 属性直接调用业务层的类。类似于 SqlDataSource 的命令属性SelectCommand、InsertCommand、UpdateCommand、DeleteCommand,ObjectDataSource 控件支持诸如 SelectMethod、UpdateMethod、InsertMethod 和 DeleteMethod属性,用于指定执行这些操作的方法名。显然ObjectDataSource是依托于一个业务逻辑类的,这样我们就可以拥有完善的架构,业务逻辑类可以为复杂的业务逻辑提供好的支持,也有利于代码的重用和维护。特别是ObjectDataSource 控件提供了EnablePaging属性、SelectCountMethod属性、StartRowIndexParameterName属性和MaximumRowsParameterName属性专门支持数据源分页。 SelectCountMethod属性指定的是获取数据项总数的方法。StartRowIndexParameterName属性用于指定一个参数的名称,如程序中不特别设定,其默认参数名为startRowIndex,该参数代表该页数据项的开始行索引;MaximumRowsParameterName属性也用于指定一个参数名称,其默认参数名为maximumRows,该参数代表一页中容纳的数据项总数。

三.示例

        本例是以SQL Server自带的Northwind数据库的Orders表为主,Employees表和Customers表为辅,显示OrderDate1997年之前的Order列表。

(1). 实体层
        在实体层中创建Order、Employee、Customer三个类,其中Order引用了Employee类和Customer类。

(2).  数据访问层
        数据访问层使用了微软提供的SqlHelper类。

public class OrderDataAccess
    {
        
private string  ConnectionString = Convert.ToString(ConfigurationManager.ConnectionStrings["NorthWindConnectionString"]);

        
public OrderDataAccess()
        {
            
//
            
// TODO: Add constructor logic here
            
//
        }

        
public int CountTotalNumber()
        {
            
return Convert.ToInt32(SqlHelper.ExecuteScalar(ConnectionString, CommandType.StoredProcedure, "Order_Select_TotalNumber")); 
        }

        
public IEnumerable FindOrders(int startRowIndex, int maximumRows,bool isDataSet)
        {
            SqlParameter[] parms 
= {
                
new SqlParameter("@StartRowIndex",SqlDbType.Int,4),
                
new SqlParameter("@MaximumRows",SqlDbType.Int,4)
            };
            parms[
0].Value = startRowIndex;
            parms[
1].Value = maximumRows;

            
if(!isDataSet)
            {
                ArrayList OrderList 
= new ArrayList();
                
using (SqlDataReader reader = SqlHelper.ExecuteReader(ConnectionString, CommandType.StoredProcedure, "Order_Select_Pagination", parms))
                {
                    
while (reader.Read())
                    {
                        OrderList.Add(LoadOrder(reader));
                    }
                }
                
return OrderList;
            }
            
else
            {
                DataSet ds 
= SqlHelper.ExecuteDataset(ConnectionString, CommandType.StoredProcedure, "Order_Select_Pagination", parms);
                
return ds.Tables[0].DefaultView;
            }
        }

        
public int DeleteOrder(int orderId)
        {
            SqlParameter parm 
= new SqlParameter("@OrderID",SqlDbType.Int,4);
            parm.Value 
= orderId;

            
return SqlHelper.ExecuteNonQuery(ConnectionString,CommandType.StoredProcedure,"Order_Delete",parm);
        }

        
private Order LoadOrder(SqlDataReader reader)
        {
            Order order 
= new Order();

            order.OrderId 
= Convert.ToInt32(reader["OrderID"]);
            order.Customer 
= new Customer(Convert.ToString(reader["CustomerID"]), Convert.ToString(reader["CompanyName"]));
            order.Employee 
= new Employee(Convert.ToInt32(reader["EmployeeID"]), Convert.ToString(reader["LastName"]), Convert.ToString(reader["FirstName"]));
            order.OrderDate 
= Convert.ToDateTime(reader["OrderDate"]);
            order.RequiredDate 
= Convert.ToDateTime(reader["RequiredDate"]);
            order.ShippedDate 
= Convert.ToDateTime(reader["ShippedDate"]);
            order.ShipVia 
= Convert.ToInt32(reader["ShipVia"]);
            order.Freight 
= Convert.ToDecimal(reader["Freight"]);
            order.ShipName 
= Convert.ToString(reader["ShipName"]);
            order.ShipAddress 
= Convert.ToString(reader["ShipAddress"]);
            order.ShipCity 
= Convert.ToString(reader["ShipCity"]);
            order.ShipRegion 
= Convert.ToString(reader["ShipRegion"]);
            order.ShipPostalCode 
= Convert.ToString(reader["ShipPostalCode"]);
            order.ShipCountry 
= Convert.ToString(reader["ShipCountry"]);

            
return order;
        }
    }

        注意FindOrders方法,它带有三个参数,startRowIndex代表起始行的索引,maxmiumRows代表本次查询所要获得的数据项总数,isDataSet是为标示是使用DataSet还是SqlDataReader,如果表示层的GridView设置了属性AllowSorting为true,也就是要求具有排序功能,那么数据访问层就必须使用DataSet来容纳数据,否则使用SqlDataReader就可以了。

(3). 存储过程

获取符合要求的总订单数存储过程:

ALTER PROCEDURE Order_Select_TotalNumber 

AS


SET NOCOUNT ON

Select Count(OrderID)

From
 Orders 

Where OrderDate < '1997'


RETURN 

分页获取数据的存储过程:

ALTER PROCEDURE Order_Select_Pagination 
(
    
@StartRowIndex int = null
,
    
@MaximumRows int = null

)
AS
SET NOCOUNT ON
DECLARE @PageLowerBound int
DECLARE @PageUpperBound int

-- Set the page bounds
SET @PageLowerBound = @StartRowIndex
SET @PageUpperBound = @PageLowerBound + @MaximumRows + 1

-- Create a temp table to store the select results
CREATE TABLE #tmp
(
     RecNo 
int IDENTITY (11NOT NULL
,
     OrderID 
int

)

INSERT INTO #tmp
        
SELECT [OrderID]

        
FROM [Orders]
        
Where OrderDate < '1997'
        
ORDER BY OrderID ASC

SELECT o.*,e.LastName,e.FirstName,c.CompanyName
FROM Orders o inner join Employees e on o.EmployeeID = e.EmployeeID inner join Customers c on o.CustomerID =
 c.CustomerID, #tmp t
WHERE o.OrderID = t.OrderID AND

     t.RecNo 
> @PageLowerBound AND
     t.RecNo 
< @PageUpperBound
ORDER BY t.RecNo

    
RETURN

(4). 业务逻辑层
        本例的业务逻辑很简单,只是作为表示层和数据访问层之间的桥梁,并没有掺杂其它的运算逻辑。
业务逻辑类中的方法可以设置给ObjectDataSource控件的SelectCountMethod属性和SelectMethod属性,这样ObjectDataSource就可以自动通过业务逻辑类获得数据了。

public class OrderBusinessLogic
    {
        
private static OrderDataAccess orderAccess = new OrderDataAccess();
        
        
public OrderBusinessLogic()
        {
            
//
            
// TODO: Add constructor logic here
            
//
        }

        
public static int GetRowsTotalNumber()
        {
            
return orderAccess.CountTotalNumber();
        }

        
public static IEnumerable GetOrdersForPagingAndSorting(int startRowIndex, int maximumRows)
        {
           
return orderAccess.FindOrders(startRowIndex, maximumRows,true);          
        }

        
public static IEnumerable GetOrdersForPaging(int startRowIndex, int maximumRows)
        {
            
return orderAccess.FindOrders(startRowIndex, maximumRows, false);
        }

        
public static void DeleteOrder(int orderId)
        {
            orderAccess.DeleteOrder(orderId);
        }       
    }

       值得注意的两点是:第一,这里的方法都是用了静态方法,其实也可以不使用静态方法。不使用静态方法时Asp.net会先实例化ObjectDataSource的TypeName中设定的类,然后调用它的方法。第二,GetOrdersForPagingAndSorting和GetOrdersForPaging两个方法,前者是为了应对GridView的排序要求,因为为了能够实现排序,必须使用DataView、DataTable或DataSet;而后者则不用于排序,只需返回一个SqlDataReader。

(5).页面程序
        如果想只使用下图所示GridView的默认分页样式,则按照下面的页面代码,不必再写任何后台代码就可实现。

   

 <asp:GridView ID="OrdersGridView" DataSourceID="OrdersObjectDataSource" AutoGenerateColumns="false"
            AllowPaging
="true" runat="server" AllowSorting="true" Width="720px" PageSize="20">
            
<PagerStyle ForeColor="Blue" BackColor="LightBlue" />            
            
<Columns>
                
<asp:BoundField HeaderText="Order Id" DataField="OrderId"  />
                
<asp:TemplateField HeaderText="Customer">
                    
<ItemTemplate>
                        
<%Eval("Customer.CompanyName")%>
                    
</ItemTemplate>
                
</asp:TemplateField>
                
<asp:TemplateField HeaderText="Employee">
                    
<ItemTemplate>
                        
<%Eval("Employee.EmployeeName")%>
                    
</ItemTemplate>
                
</asp:TemplateField>
                
<asp:BoundField HeaderText="Order date" DataField="OrderDate" DataFormatString="{0:g}" />
                
<asp:BoundField HeaderText="Required date" DataField="RequiredDate" DataFormatString="{0:d}" />
                
<asp:BoundField HeaderText="Shipped date" DataField="ShippedDate" DataFormatString="{0:d}" />
                
<asp:BoundField HeaderText="Ship address" DataField="ShipAddress" />
                
<asp:BoundField HeaderText="Ship country" DataField="ShipCountry" />                
            
</Columns>
        
</asp:GridView>
        
<asp:ObjectDataSource ID="OrdersObjectDataSource" runat="server" SelectCountMethod="GetRowsTotalNumber"
          SelectMethod
="GetOrdersForPaging" TypeName="MyTest.BusinessLogic.OrderBusinessLogic" OldValuesParameterFormatString="Original_{0}" EnablePaging="true">          
        
</asp:ObjectDataSource>

        如果想实现如下图所示的自定义的分页样式,则参考下列代码:

<asp:GridView ID="OrdersGridView" DataSourceID="OrdersObjectDataSource" AutoGenerateColumns="false"
            AllowPaging
="true" AllowSorting="true" OnDataBound="OrdersGridView_DataBound" runat="server" 
            Width
="720px" PageSize="25" OnRowDeleted="OrdersGridView_RowDeleted" DataKeyNames="OrderID">
            
<Columns>
                
<asp:BoundField HeaderText="Order Id" DataField="OrderID" SortExpression="OrderID"/>
                
<asp:BoundField HeaderText="Customer company" DataField="CompanyName" SortExpression="CompanyName"/>
                
<asp:TemplateField HeaderText="Employee" SortExpression="EmployeeName">
                  
<ItemTemplate>
                      
<%Eval("LastName")+" "+Eval("FirstName"%>  
                  
</ItemTemplate>
                
</asp:TemplateField>                                                
                
<asp:BoundField HeaderText="Order date" DataField="OrderDate" DataFormatString="{0:g}" SortExpression="OrderDate"/>
                
<asp:BoundField HeaderText="Required date" DataField="RequiredDate" DataFormatString="{0:d}" SortExpression="RequiredDate"/>
                
<asp:BoundField HeaderText="Shipped date" DataField="ShippedDate" DataFormatString="{0:d}" SortExpression="ShippedDate"/>
                
<asp:BoundField HeaderText="Ship address" DataField="ShipAddress" />
                
<asp:BoundField HeaderText="Ship country" DataField="ShipCountry" />                
                
<asp:CommandField ButtonType="Button" DeleteText="删除" ShowDeleteButton="true" HeaderText="Operation"  />
            
</Columns>
            
<PagerStyle ForeColor="Blue" BackColor="LightBlue" />            
            
<PagerTemplate>
                
<table width="100%">
                    
<tr>
                        
<td width="70%">
                            
<asp:Label ID="MessageLabel" ForeColor="Blue" Text="页码:" runat="server" />
                            
<asp:DropDownList ID="PageDropDownList" AutoPostBack="true" OnSelectedIndexChanged="PageDropDownList_SelectedIndexChanged"
                                runat
="server" />
                            
<asp:LinkButton CommandName="Page" CommandArgument="First" ID="linkBtnFirst" runat="server">首页</asp:LinkButton>
                            
<asp:LinkButton CommandName="Page" CommandArgument="Prev" ID="linkBtnPrev" runat="server">上一页</asp:LinkButton>
                            
<asp:LinkButton CommandName="Page" CommandArgument="Next" ID="linkBtnNext" runat="server">下一页</asp:LinkButton>
                            
<asp:LinkButton CommandName="Page" CommandArgument="Last" ID="linkBtnLast" runat="server">末页</asp:LinkButton>
                        
</td>
                        
<td align="right">
                            
<asp:Label ID="CurrentPageLabel" ForeColor="Blue" runat="server" />
                        
</td>
                    
</tr>
                
</table>
            
</PagerTemplate>
        
</asp:GridView>
        
<asp:ObjectDataSource ID="OrdersObjectDataSource" runat="server" SelectCountMethod="GetRowsTotalNumber"
          SelectMethod
="GetOrdersForPagingAndSorting" DeleteMethod="DeleteOrder"
          TypeName
="MyTest.BusinessLogic.OrderBusinessLogic" EnablePaging="true" EnableViewState="true">          
        
<DeleteParameters>
            
<asp:Parameter Name="OrderId" Type="Int32" Direction="Input" />
        
</DeleteParameters>
        
</asp:ObjectDataSource>

        注意页导航模板PagerTemplate属性的使用。通常将按钮控件(如上面代码中的LinkButton)添加到页导航模板以执行分页操作。单击 CommandName 属性设置为“Page”的按钮控件时,GridView 控件会执行分页操作。按钮的 CommandArgument 属性确定要执行的分页操作的类型。下表列出了 GridView 控件支持的命令参数值。 

CommandArgument 值

说明

“Next”

导航至下一页。

“Prev”

导航至上一页。

“First”

导航至第一页。

“Last”

导航至最后一页。

整数值

导航至指定页码。

        页面程序的后台代码:

public partial class TestGridview2 : System.Web.UI.Page
{
    
protected void Page_Load(object sender, EventArgs e)
    {

    }

    
protected void PageDropDownList_SelectedIndexChanged(Object sender, EventArgs e)
    {
        GridViewRow pagerRow 
= OrdersGridView.BottomPagerRow;
        DropDownList pageList 
= (DropDownList)pagerRow.Cells[0].FindControl("PageDropDownList");
        OrdersGridView.PageIndex 
= pageList.SelectedIndex;
    }

    
protected void OrdersGridView_DataBound(Object sender, EventArgs e)
    {
        GridViewRow pagerRow 
= OrdersGridView.BottomPagerRow;
        LinkButton linkBtnFirst 
= (LinkButton)pagerRow.Cells[0].FindControl("linkBtnFirst");
        LinkButton linkBtnPrev 
= (LinkButton)pagerRow.Cells[0].FindControl("linkBtnPrev");
        LinkButton linkBtnNext 
= (LinkButton)pagerRow.Cells[0].FindControl("linkBtnNext");
        LinkButton linkBtnLast 
= (LinkButton)pagerRow.Cells[0].FindControl("linkBtnLast");
        
if (OrdersGridView.PageIndex == 0)
        {
            linkBtnFirst.Enabled 
= false;
            linkBtnPrev.Enabled 
= false;
        }
        
else if (OrdersGridView.PageIndex == OrdersGridView.PageCount - 1)
        {
            linkBtnLast.Enabled 
= false;
            linkBtnNext.Enabled 
= false;
        }
        
else if (OrdersGridView.PageCount <= 0)
        {
            linkBtnFirst.Enabled 
= false;
            linkBtnPrev.Enabled 
= false;
            linkBtnNext.Enabled 
= false;
            linkBtnLast.Enabled 
= false;
        }
        DropDownList pageList 
= (DropDownList)pagerRow.Cells[0].FindControl("PageDropDownList");
        Label pageLabel 
= (Label)pagerRow.Cells[0].FindControl("CurrentPageLabel");
        
if (pageList != null)
        {
            
for (int i = 0; i < OrdersGridView.PageCount; i++)
            {
                
int pageNumber = i + 1;
                ListItem item 
= new ListItem(pageNumber.ToString() + "/" + OrdersGridView.PageCount.ToString(), pageNumber.ToString());
                
if (i == OrdersGridView.PageIndex)
                {
                    item.Selected 
= true;
                }
                pageList.Items.Add(item);
            }
        }
        
if (pageLabel != null)
        {
            
int currentPage = OrdersGridView.PageIndex + 1;
            pageLabel.Text 
= "当前页: " + currentPage.ToString() +
              
" / " + OrdersGridView.PageCount.ToString();
        }
    }

    
protected void OrdersGridView_RowDeleted(object sender,GridViewDeletedEventArgs e)
    {
        
if (e.Exception == null && OrdersGridView.Rows.Count == 1)
        {
            
// we just deleted the last row
            OrdersGridView.PageIndex = Math.Max(0, OrdersGridView.PageIndex - 1);
         }
    }
}

    
    由以上示例可以看出GridView关联ObjectDataSource时,省去了DataBind方法的使用。也就是说只要给GridView关联上数据源控件,那么绑定的事程序员就不用操心了。当GridView发生翻页事件时整个的运行过程是这样的,GridView的PageIndex变成新值,ObjectDataSource根据GridView的PageIndex属性换算出startRowIndex和MaxmiumRows的值,然后传递这两个参数给SelectMethod指定的方法从而获得数据,然后再调用SelectCountMethod指定的方法获得总数据项数,以计算出总页数,然后执行OrderGridView_DataBound事件处理方法最终完成绑定工作。
    注意OrdersGridView_RowDeleted事件处理方法的写法,它是为了应对将最后一页的最后一条数据删除之后,GridView将能够识别出最后一页已经被删空了,因此原来的倒数第二页就变成了现在的末页了,并让GridView的当前页指向末页。
    下载完整源程序/Files/taewind/TestDataBindControlls.rar

posted on 2006-12-12 17:19  taewind  阅读(...)  评论(... 编辑 收藏

导航

统计