这篇文章讲述了如何利用SQL Server 2005的新特性来简单高效的实现分页。对于那些暂时还没用到SQL Server2005的人们,请看在大规模数据中的高效分页方法。如果需要,这篇文章会补上这里讲到的内容。

  出处:http://aspnet.4guysfromrolla.com/demos/printPage.aspx?path=/articles/031506-1.aspx

  介绍

  web开发中普遍会用页面来显示数据。比起整页显示一张报表或者一张数据表的数据给用户,开发者经常用到的是分页显示,每页只显示部分数据,用翻页来控制。在ASPV.NET 1.X里,DataGrid控件使翻页显示变得简单—只需要把属性AllowPaging设置为”true”,并在PageIndexChanged事件中加少量几行的代码就可以实现!ASP.NET 2.0中的控件GridView使事件也简单化了,只需要在GridView的智能面板里把“允许分页”选中,不需要一行代码就可以实现。

  当然了,生活中没有任何事是容易就能做好的。你需要权衡选中一个复选框就能实现的分页方案(或者用DataGrid,写几行代码的实现方案)的性能。如果不选中分页,DataGrid和GridView使用默认分页,简单的把所有数据从头到尾地显示在一张网页上。当数据量小时,几十一百或类似的数量时,效率性能不会太明显。但是,如果你想像这样用默认的分页方法显示上千万、以至几十万的书时就不可行了。

  取代默认分页的方法就是自定义分页,你要做的工作就是用代码来判断并把正确的分页数据取出,可能会费点事,但对于应付如此庞大的数据量来说,绝对值得。我在《ASP.NET Web数据控件快速开发》中讨论了如何在ASP.NET 1.X中实现自定义分页,这篇文章,我们来看在ASP.NET 2.0中如何利用SQL Server 2005的新特性 ROW_NUMBER()来实现自定义分页。(更多关于SQL SERVER的新特性说明,请看利用Microsoft SQL SERVER 2005返回列值。)

  继续读下去来学习更多吧!

  默认分页与自定义分页对比

  在ASP.NET 2.0里GridView(或ASP.NET 1.X里的DataGrid)提供两种分页模型:默认分页与自定义分页。这两种模型在性能与易用性上提供了折中的方案。SqlDataSource控件使用默认分页(尽管你可以在自定义分页了使用它);ObjectDataSource默认使用默认分页模型,不过有个简单的配置可以让它使用自定义分页。心里要时刻记得GridView仅仅是显示数据;它才是GridView负责从数据库中检索数据的数据源控件。

  使用默认分页,每次打开新页显并显示时,都要从GridView的数据源控件中获得所有数据。一旦全部的数据返回,GridView就把所有的数据全显示在页面上,于是用户看到一张页面上显示了如此多的数据。关键要理解这里,无论何时当用户访问第一页或翻到其他页时,所有的数据都会被重新加载一遍。

  举个例子,比如说你在一家电子商务公司上班,你想让用户分页查看你们公司所销售的150中产品。并且,你想每页只显示10条数据。现在,当一个用户访问网页时,所有150条数据都被数据源空间返回过来。但GridView只显示第一个10条数据(产品1到产品10)。再想象一下,当这个用户翻看第二页的数据时,这会引发一个回发的事件,而且在这个时候GridView又会从数据源控件中获得所有的150条记录,但这时只需要显示第二个10条(产品11到产品20)。

缓存与SqlDataSource
SqlDataCourse在对属性EnableCaching做简单设置后允许数据集缓存数据。对于一个缓存的数据集,翻页时不需要再访问数据库,从分页开始到结束都缓存在内存里。然而,初始化页面时相同的问题发生了—所有的数据必须载入到缓存的数据集里去。此外,这样的话你还必须担忧那些过期的数据(虽然你使用了SQL cache dependencies ,但在这里已经没意义了)。

  在我不太科学的测试下,发现缓存数据和自定义分页的速度差2倍以上……尽管,你看到缓存方式接近不缓存的方式。(但它始终没有超过自定义分页!)

  更多关于SqlDataSource的数据集缓存,请看利用SqlDataSource缓存数据.

  使用自定义分页,你,开发者,需要做一些工作,但比起盲目的把GridView绑定到一个数据源控件,并且选中“允许分页”复选框,倒不如配置一下数据源控件,使它只检索某一页需要显示的部分数据。这样做的好处是,当显示第一页的数据时,你可以写一段只检索产品1到产品10的sql语句,而不是把所有150条记录全取出来。不过,你的sql语句需要“聪明”的知道怎么把需要的数据从150条记录里剪切出来。

自定义分页的性能优势
我们可以从数据记录的检索能看出自定义分页比默认分页的性能好。在我们的例子里,假设有150条产品数据,每页显示10条。如果用自定义分页,用户挨个浏览这15页的数据,150条数据会分批检索出来;如果用自定义分页,不管哪页150条数据都会被检索出来,导致全部的数据检索量也许是15倍的15条,可能有2250条之多!

  虽然自定义分页的性能显而易见,但默认分页却非常简单。所以当数据量小并且数据库服务器的负载也不太重时,我推荐你使用默认分页。如果你有几百、上千以至上万条数据需要分页显示时,一定要使用自定义分页。当然,像分页ASPFAQ.com的数据库,现在的数据量只有200条问答,用默认分页足够了。(当然,如果你觉得数据量将来不会增长,比如可能会维持在75条左右,那你就使用默认分页。但如果将来数据会增长到7500条而你却使用了默认分页,一定会有些客户不高兴的!)

  利用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()从开始的索引行数到加每页最大条数的行数一部分数据。

  为了使表示跟具体,我们来看下下面这个例子。假设我们有一个表Employees有5000条数据(公司很不错哦!),看下面的查询语句:

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查询结果

  默认分页

  (从Employees检索所有记录)

持续时间 (秒) 读取数量
1.455 383
1.405 383
1.434 383
1.394 383
1.365 383
平均: 1.411 平均: 383

  自定义分页

  (从Employees检索一页记录)

持续时间 (秒) 读取数量
0.003 29
0.000 29
0.000 29
0.003 29
0.003 29
平均: 0.002 平均: 29

  

  ASP.NET测试结果

默认分页
(从Employees查询所有记录)
页面载入时间(秒)
2.34136852588807
2.35772228034569
2.43368277253115
2.43237562315881
2.33167064529151
平均: 2.379363969

自定义分页
(从Employees查询一页记录)
页面载入时间(秒)
0.0259611207569677
0.0280046765720224
0.0359054013848129
0.0295534767686955
0.0300096800012292
平均: 0.029886871

SqlDataSource缓存
(查询所有记录,但缓存它们)
页面载入时间(秒)
2.39666633608461
0.0431529705591074
0.0443528437273452
0.0442313199023898
0.0491523364002967
平均: 0.515511161

  正如你看到的,自定义分页要比默认分页快2倍以上。在相同的数据量下,GetEmployeesSubset(@startRowIndex int,@maximumROws int)要比简单的从Employees表中SELECT查询出所有记录要快470倍。在相同的ASP.NET环境下,自定义分页要比默认分页快120倍。.高负荷的工作量使两者接近,也就是说建立数据库连接和分配任务都会降低性能。不管怎样,在性能方面两个数量级相差太大了。并且这种差距会随着数据量的增加或服务器的性能不同使载入更加明显。

  当缓存是空的时,SqlDataSource方案消耗时间很长,因为它必须到数据库去取得所有数据,缓存在服务器。而服务器有空闲资源时才重新载入(如果只有少量的资源,缓存的数据集就会被清除出内存)并且不再载入。在数据被缓存后,虽然在性能上很接近自定义分页,但0.516秒的平均时间会随着越来越多的缓存数据而接近0.05秒。

  总结

  ASP.NET 1.x中的DataGrid和2.0中的GridView有两种分页方案:默认分页与自定义分页。默认分页很容易做到,但每次访问每个页面都会访问数据库并取出所有数据。而自定义分页非常高效,灵活地取出该取出的那些数据。SQL SERVER 2005由于它的新特性ROW_NUMBER()能取出行数的能力,使取出某段数据简单了。

  如果你做的WEB程序现在或将来有可能让用户翻页查看庞大的数据,那么你用自定义分页是很合适的。

  快乐开发!

posted on 2009-04-08 10:39  钱途无梁  阅读(490)  评论(0编辑  收藏  举报