一.什么是自定义分页
自定义分页是与默认分页相对应的。默认分页指一次检索出所有数据并将其绑定到数据绑定控件中,虽然该控件只能一页一页显示这些数据,但是所有数据其实都已经被绑定到控件上了。自定义分页的含义是显示到哪一页就检索并绑定哪一页的数据。显然在大数据量的情况下,自定义分页的效率会高很多。
在Asp.net 1.x中自定义分页又称数据库分页,DataGrid中的AllowCustomerPaging属性和VirtualItemCount属性就是专门为自定义分页准备的。在Asp.net 2.0中因为引入了数据源的概念,因此自定义分页也可以叫做数据源分页。
二.为什么使用ObjectDataSource
ASP.NET2.0提供了SqlDataSource数据源控件,提供了ConnectionString、SelectCommand、SelectCommandType、SelectParameters等属性,分别用于指定连接字符串、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表为辅,显示OrderDate在1997年之前的Order列表。
(1). 实体层
在实体层中创建Order、Employee、Customer三个类,其中Order引用了Employee类和Customer类。
(2). 数据访问层
数据访问层使用了微软提供的SqlHelper类。
{
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). 存储过程
获取符合要求的总订单数存储过程:
AS
SET NOCOUNT ON
Select Count(OrderID)
From Orders
Where OrderDate < '1997'
RETURN
分页获取数据的存储过程:
(
@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 (1, 1) NOT 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就可以自动通过业务逻辑类获得数据了。
{
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的默认分页样式,则按照下面的页面代码,不必再写任何后台代码就可实现。
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>
如果想实现如下图所示的自定义的分页样式,则参考下列代码:
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” |
导航至最后一页。 |
整数值 |
导航至指定页码。 |
页面程序的后台代码:
{
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