这篇文章讲述了如何利用SQL Server 2005的新特性来简单高效的实现分页。对于那些暂时还没用到SQL Server2005的人们,请看在大规模数据中的高效分页方法。如果需要,这篇文章会补上这里讲到的内容。
出处:http://aspnet.4guysfromrolla.com/demos/printPage.aspx?path=/articles/031506-1.aspx
介绍
继续读下去来学习更多吧!
默认分页与自定义分页对比
在ASP.NET 2.0里GridView(或ASP.NET 1.X里的DataGrid)提供两种分页模型:默认分页与自定义分页。这两种模型在性能与易用性上提供了折中的方案。SqlDataSource控件使用默认分页(尽管你可以在自定义分页了使用它);ObjectDataSource默认使用默认分页模型,不过有个简单的配置可以让它使用自定义分页。心里要时刻记得GridView仅仅是显示数据;它才是GridView负责从数据库中检索数据的数据源控件。
使用默认分页,每次打开新页显并显示时,都要从GridView的数据源控件中获得所有数据。一旦全部的数据返回,GridView就把所有的数据全显示在页面上,于是用户看到一张页面上显示了如此多的数据。关键要理解这里,无论何时当用户访问第一页或翻到其他页时,所有的数据都会被重新加载一遍。
缓存与SqlDataSource |
SqlDataCourse在对属性EnableCaching做简单设置后允许数据集缓存数据。对于一个缓存的数据集,翻页时不需要再访问数据库,从分页开始到结束都缓存在内存里。然而,初始化页面时相同的问题发生了—所有的数据必须载入到缓存的数据集里去。此外,这样的话你还必须担忧那些过期的数据(虽然你使用了SQL cache dependencies ,但在这里已经没意义了)。
在我不太科学的测试下,发现缓存数据和自定义分页的速度差2倍以上……尽管,你看到缓存方式接近不缓存的方式。(但它始终没有超过自定义分页!) 更多关于SqlDataSource的数据集缓存,请看利用SqlDataSource缓存数据. |
使用自定义分页,你,开发者,需要做一些工作,但比起盲目的把GridView绑定到一个数据源控件,并且选中“允许分页”复选框,倒不如配置一下数据源控件,使它只检索某一页需要显示的部分数据。这样做的好处是,当显示第一页的数据时,你可以写一段只检索产品1到产品10的sql语句,而不是把所有150条记录全取出来。不过,你的sql语句需要“聪明”的知道怎么把需要的数据从150条记录里剪切出来。
自定义分页的性能优势 |
我们可以从数据记录的检索能看出自定义分页比默认分页的性能好。在我们的例子里,假设有150条产品数据,每页显示10条。如果用自定义分页,用户挨个浏览这15页的数据,150条数据会分批检索出来;如果用自定义分页,不管哪页150条数据都会被检索出来,导致全部的数据检索量也许是15倍的15条,可能有2250条之多!
|
利用SQL Server 2005高效地取回一页数据
像早前4Guys 的一篇文章讲述的那样,利用Microsoft SQL Server 2005获得行值中提到的, SQL Server 2005 引入了许多返回行值的关键词。特别提到关键词the ROW_NUMBER()可以返回一列递增的行数。因此,我们可以使ROW_NUMBER()写一条像下面的sql查询语句来获得某页的数据:
SELECT ...
FROM
(SELECT ...
ROW_NUMBER() OVER(ORDER BY ColumnName) as RowNum
FROM Employees e
) as DerivedTableName
WHERE RowNum BETWEEN @startRowIndex AND (@startRowIndex + @maximumRows) - 1
这里的@startRowIndex表示开始行的索引,@maximumRows表示每页显示的最大条数。这条语句会返回ROW_NUMBER()从开始的索引行数到加每页最大条数的行数一部分数据。
SELECT RowNum, EmployeeID, LastName, FirstName
FROM
(SELECT EmployeeID, LastName, FirstName
ROW_NUMBER() OVER(ORDER BY EmployeeID) as RowNum
FROM Employees
) as EmployeeInfo
返回的结果如下:
RowNum | EmployeeID | LastName | FirstName |
1 | 1000 | Smith | Frank |
2 | 1001 | Jackson | Lucy |
3 | 1011 | Lee | Sam |
4 | 1012 | Mitchell | Jisun |
5 | 1013 | Yates | Scott |
6 | 1016 | Props | Kathryn |
... | |||
5000 | 6141 | Jordan | DJ |
注意即使EmployeeID字段中间可能有短缺或者可能不是从1开始的,ROW_NUMBER()也会从第1条记录开始,并且稳定增长。因此,如果你想每页显示10条数据,我们想看第3页时,我们知道我们需要的数据是第31条到第40条,我们可以用一个简单的WHERE查询得到。
配置ObjectDataSource支持自定义分页
正如前面提到的,一方面,SqlDataSource并没有设计成能提供自定义分页;另一方面,ObjectDataSource被设计成了能支持这个方案。ObjectDataSource被设计成能从一个object读取数据的数据源控件。这个object不管从哪、怎样取出数据,像从一个web 服务、一个数据库、一个文件系统,或一个XML文件……等等。ObjectDataSource并不关心,它就像在数据存储者和数据需要者(像一个GridView控件)之间充当了一个中介。(更多关于ObjectDataSource请看ObjectDataSource控件概述)
当把一个Web数据控件绑定到一个ObjectDataSource并设置为“允许分页”时,如果你没有具体设置ObjectDataSource来支持自定义分页,会使用默认方案来分页。要使ObjectDataSource支持自定义分页,你需要使一个object来提供以下这些方法:
1. 一个包含两个整型参数的方法。第一个整数表示检索数据开始的索引位置(起点),而第二个整数表示每页显示的最大条数。这个方法在调用时会返回我们所请求的数据,也就是从检索开始位置到往后每页显示最大条数的准确记录,而不是把所有数据记录都取出来。
2. 一个返回全部分页的数据记录数量(整数类型)的方法。(这是Web数据控件在在初始化时需要的,这样就可以显示出要分出的总页数或者是是否还需要有下一页。)
如果你要用到基础的object类型,配置ObjectDataSource来实现分页是很简单的,来看一下下面的这些ObjectDataSource的属性:
把EnablePaging设置为True
把SelectMethod设置为提供开始页和开始最大行数的参数以检索数据的方法
把StartRowIndexParameterName设置为你在SelectMethod中用到的表示检索开始位置的变量;如果你不设置这个,它默认为取startRowIndex的值
把MaximumRowsParameterName设置为你在SelectMethod用到的每页最大记录数,如果你不设置这个,它默认取maximumROws的值
把SelectCountMethod设置为返回分页的所有记录的总数的方法
就这么简单。如果你像这样配置了,那么ObjectDataSource就使用自定义分页了。当然,难点是创建能正确的取出数据的基础对象。但你一旦有了这个基础对象,要实现自定义分页也就配置一下ObjectDataSource的几个属性而已。
创建一个支持自定义分页的对象
为了使一个ObjectDataSource绑定到一个GridView,我们需要一个能灵活取得需要数据的底层对象,这个对象还能够返回需要分页的记录数。像约瑟夫校长的文章中写的那样, 《在Visual Studio 2005和中ASP.NET 2.0使用强对象类型》和布莱恩·诺伊斯的文章 《利用Visual studio 2005 数据集设计器做数据访问层》中讲述的那样,在Visual studio 2005中创建能绑定到ObjectDataSource上的对象集是一件很容易的事情。首先要定义取出数据集的存储过程(或sql查询语句),以便让那些强类型的数据集返回数据。
接下来,在本文的最后提供的,有一个包含5000条职员的范例数据库(大批量增加记录是很容易的)。这个数据库包含了2个自定义分页示例中要用到的3个存储过程:
GetEmployeesSubset(@startRowIndex int, @maximumRows int) – 返回按EmployeeID排序后从@startRowIndex开始的最多@maximumRows条记录。
GetEmployeesRowCount – 返回Employees表里的记录总数。
GetEmployeesSubsetSorted(@sortExpression nvarchar(50), @startRowIndex int, @maximumRows int) – 这个存储过程返回一页经过指定条件排序的数据。也就是说能返回比如支持薪水排序后的记录。(GetEmployeesSubset只能返回按EmployeeID排序的记录。)这个扩展当你需要使用自定义分页中有自定义排序时会用到。
在这篇文章里我们不计划讲述关于自定义分页中的自定义排序,尽管在这篇文章的下载中也提供了这样的例子;可以通过《自定义分页中的数据排序》看到如何建立自定义分页并支持双向排序……
创建完这些存储过程以后,我在我的项目里加入一个强类型的数据集文件(Employees.xsd)。然后在里面加入三个与三个存储过程相对应的方法。最后我加入了一个返回EmployeesTableAdapter对象的方法GetEmployeesSubset(startRowIndex,maximumRows)和一个可以设置到ObjectDataSource属性上的方法GetEmployeesRowCount()。(关于创建强类型数据集的详细步骤去看《在Visual Studio 2005和 ASP.NET中使用强类型数据集》和思考特·格思里的博客文章《在VS2005和ASP.NET 2.0中用强类型数据集创建数据访问层》。)
默认分页与自定义分页性能比较
在本文最后有关于默认分页与自定义分页的性能比较的数据库(包含一张5000条数据的表),我用SQL和ASP.NET找出性能的差距。(这些测试在我电脑上做是很不严谨的,因为我的电脑还同时运行着其他的程序,虽然结果不能被称做证明,但我想自定义分页的性能在比较中肯定是有优势的。)
SQL查询结果 | |||||||||||||||||||||||||||||||||||||
|
|
ASP.NET测试结果 | |||||||||||||||||||||||||||||
|
|
|
正如你看到的,自定义分页要比默认分页快2倍以上。在相同的数据量下,GetEmployeesSubset(@startRowIndex int,@maximumROws int)要比简单的从Employees表中SELECT查询出所有记录要快470倍。在相同的ASP.NET环境下,自定义分页要比默认分页快120倍。.高负荷的工作量使两者接近,也就是说建立数据库连接和分配任务都会降低性能。不管怎样,在性能方面两个数量级相差太大了。并且这种差距会随着数据量的增加或服务器的性能不同使载入更加明显。
总结
ASP.NET 1.x中的DataGrid和2.0中的GridView有两种分页方案:默认分页与自定义分页。默认分页很容易做到,但每次访问每个页面都会访问数据库并取出所有数据。而自定义分页非常高效,灵活地取出该取出的那些数据。SQL SERVER 2005由于它的新特性ROW_NUMBER()能取出行数的能力,使取出某段数据简单了。
如果你做的WEB程序现在或将来有可能让用户翻页查看庞大的数据,那么你用自定义分页是很合适的。
快乐开发!