ASP.NET中的分页剖析-基于SQL语句的分页和基于PagedDataSource的分页
在ASP.NET中,要对数据进行分页无外乎有两种形式,一种是使用控件的内置分页功能进行分页,再一种就是使用自定义方式进行分页。自定义分页又可以分为两种方式,一种是基于SQL语句的分页,另一种是基于ASP.NET提供的PagedDataSource分页类进行分页。
使用控件的内置功能进行分页,也就是控件本身给提供了分页功能,我们直接启用分页即可,进行相关的一些简单设置即可完成分页,譬如GridView控件就提供了内置的分页功能。使用自定义方式进行分页,主要分为基于SQL语句的分页和基于ASP.NET提供的PagedDataSource分页类进行分页,譬如DataList和Repeater控件就没有提供内置的分页功能,如果需要对它们进行分页就需要用这种方式了。下面简单分析下它们的优缺点:基于SQL语句的分页方式每次返回数据量较少(仅返回当前页面所需数据),适合于大数据量(百万行数据量级)的数据进行分页,只是代码编写比较复杂,后面再进行详细介绍编码。ASP.NET提供的PagedDataSource分页类使用比较方便,封装了分页常规的属性,但由于一次将所有的数据读入,比较占用资源,一般适合数据量较少(一般不超过一万行),并且数据库服务器和Web服务器之间带宽不是瓶颈时使用(同一台计算机上不会有带宽问题)。ASP.NET提供的PagedDataSource分页类和控件提供的内置分页功能实现机理是一致的,当点击页面时,每次数据都会全部读入,这样,当数据量大时,效率较低,这时就应该使用基于SQL语句的自定义分页方式。
由于分页功能的使用频率较高,为了提高代码的复用效果,后面把分页做成用户控件供以后方便使用,这里主要是基于SQL语句的这种分页去做的,全靠手工方式去实现分页效果。在用户使用的过程中,由于翻页功能使用的频率也很高,所以这里再把基于SQL语句的分页实现使用存储过程的方式实现,使之效率更高。
下面先简单介绍一下这两种分页方式。
一、基于SQL语句的分页---
一般使用存储过程实现。它通过编写SQL查询,对数据进行筛选,仅返回当前页面所需的内容数据。要进行分页,首先必须要知道每页显示多少条(PageSize)、要显示第几页(CurrentPageIndex)。这是我们分页的前提内容。现在假设如果查询得到的结果集是有编号的,那我们可以很方便地计算得到所需的内容,比如我们要以每页10条记录显示第三页的数据,就可以直接查询编号为21~30之间的所有记录就行了。不过很可惜,SQL语句并没有结果编号的功能,但是我们可以使用Top语句和子查询,所以我们可以首先依据排序条件查询前20条记录,然后查询不包含前20条记录的部分中的前10条的记录。根据这个思路,我们可以编写SQL语句如下所示:
select top PageSize * from 表 where 条件 and id not in (select top PageSize * (CurrentPageIndex - 1) id from 表 where 条件 order by 排序条件_ABC) order by 排序条件_ABC
select top 10 * from 表 where id not in (select top 20 id from 表 order by 排序条件_ABC) order by 排序条件_ABC
有了它,我们就可以为每个分页的查询编写相对应的查询语句了。这里要强调的是:使用Top语句获取记录时必须要进行排序,要不然SQL系统会给我们随机的获取数据的,并不是我们要的排序的结果,还有In子句中的Order By排序不会对影响主句,所以还需要在主句中也要进行Order By进行排序,反之亦然。
事实上,在SQL Server 2005及以后的版本中,增加了一种新的函数ROW_NUMBER()可以返回行号,所以可以使用如下的查询方式返回同样的结果。
select * from (select ROW_NUMBER() OVER(ORDER BY 排序条件_ABC) as LineNO, * from 表) as Rank where LineNO between 21 and 30 order by 排序条件_ABC
下面再说下基于PagedDataSource类的分页。
二、基于PagedDataSource类的分页---
刚才我们了解了基于SQL语句的分页,事实上,我们有更为简便通用的方法,那就是可以使用分页类PagedDataSource。该类封装了数据绑定控件与分页相关的属性,
常用的属性见下表:
属 性 |
说 明 |
CurrentPageIndex |
当前页 |
PageCount |
总页数 |
Count |
总记录数 |
PageSize |
每页记录数 |
DataSource |
数据源 |
AllowPaging |
控件是否实现自动分页功能 |
只要将数据源和当前页码赋值给PagedDataSource类的实例对象,其他属性(总记录数和总页数)可以自动计算得出。
可以编写绑定的方法如下:
private void MyDataBind()
{
PagedDataSource pdsBooks = new PagedDataSource();
//对PagedDataSource 对象的相关属性赋值
pdsBooks.DataSource = BookManager.GetOrderedSmallBooksByCategoryId(Convert.ToInt32(ViewState["typeid"]), (string)ViewState["Order"]);
pdsBooks.AllowPaging = true;
pdsBooks.PageSize = 4;
pdsBooks.CurrentPageIndex = Pager;
lblCurrentPage.Text = "第 " + (pdsBooks.CurrentPageIndex + 1).ToString() + " 页 共 " + pdsBooks.PageCount.ToString()+" 页";
SetEnable(pdsBooks);
//把PagedDataSource 对象赋给DataList控件
dlBooks.DataSource = pdsBooks;
dlBooks.DataBind();
}
从上面的代码可以看出,使用PagedDataSource分页类的使用过程是:
1) 指定PagedDataSource实例对象的数据源为GetOrderedSmallBooksByCategoryId返回的数据集合;
2) 分别设置允许分页(AllowPaging)、页大小(PageSize)、当前页(CurrentPageIndex)的属性;
3) 指定数据显示控件的数据源为该实例对象,并绑定。
关于页面信息保持问题: 现在就可以编写方法实现分页和排序了。不过还需要进行状态保持,当前页数和排序条件需要进行状态保持。因为该分页和排序信息仅需要在该页面有效,所以使用Session、Cookie、Application的状态保持方式并不合适。可以使用一个页面级的状态保持方式:ViewState。使用页面级状态保持的好处就是不影响其他页面的分页。
使用语法:
ViewState[“名称”]=值
事实上,ViewState状态保持的方式是在页面上放置一个隐藏域:
<input type=”hidden” name=”__VIEWSTATE” value=””>
每次数据回传,该隐藏域的内容也一起回传,从而进行状态信息的保持。