After I posted a previous article comparing between many methods of Paging in ASP and ASP.Net, I wanted to focus on how to tweak paging using DataSet as it is unpopular tip and it saves lots of time to return records in closer pages which usually gets more hits than further pages...
The method DbDataAdapter.Fill(dataSet As DataSet, startRecord As Integer, maxRecords As Integer, srcTable As String) specifies the start record and number of records to fill the DataSet with, and of course you need to run a count query first to calculate PageCount'Count Query
Cmd.CommandText = "Select count(ID) from Users where.."
Count = CInt(Cmd.ExecuteScalar())
PageCount = Math.Ceiling(Count / PageSize)
'Select Query
cmd.CommandText = "Select Top " & (PageIndex * PageSize) & " * from Users where.. Order By.."
Dim DS As New DataSet
Dim DA As DbDataAdapter = PFactory.CreateDataAdapter()
DA.SelectCommand = Cmd
DA.Fill(DS, (PageIndex - 1) * PageSize, PageSize, SrcTable)
Dim DT As New DataTable= DS.Tables(SrcTable)
DA = Nothing
DS = nothing
For Each Row As DataRow In DT.Rows
'Do Something
Next
DT = Nothing
MSDN @ [ADO.Net & Paging Through a Query Result ] Says:
This might not be the best choice for paging through large query results because, although the DataAdapter fills the target DataTable or DataSet with only the requested records, the resources to return the entire query are still used ..That is true, At first testing the time results for this method was bad even for closer pages..
Remember that the database server returns the entire query results even though only one page of records is added to the DataSet.
To tweak that: I added a top clause in the select query to select Top(PageSize * PageIndex), so if we want the 2nd page where page size is 10: the database returns only 20 records, and the DataSet is filled with the 2nd 10 records.. instead of letting the database to return the entire table. and this tweak makes DataSet timing chart similar to 'Top Clause' SQL paging method [ more on that @ Paging in ASP and ASP.Net]
A very important method of increasing data access efficiency is to cache frequently accessed data. like country list or top articles.. in situations where the data changes less frequently, such as once a day or every few hours, it is inefficient to hit the database for every page request.
As many would suggest to cache the HTML Output of a portion of a page(Fragment Caching) as a country select box or caching the entire page(Output Caching), there are cases where Data Caching will be more powerful. for example you may need to display that list of countries in more than one page/Portion with different look and/or use that list programmatically.
In a previous post [Data Access Layer for Classic ASP]: I proposed DAL Classes to encapsulate Data Access code for a certain Table/Object, and now I'll extend the DAL to handle Data caching too, please refer to my previous post before continuing..
I've modified the "Users" class to handle saving and reading from cache, which is saved to ADO XML format or Advanced Data Tablegram (ADTG) Binary format using "Save" method of ADO Recordset, Which has many advantages :
- You don't need to change much of your Data Access code to read from cache
- Opening the recordset from XML will act the same as Opening Database when you use Paging
- You can use The "Filter" method of ADO Recordset to filter records as you would do in a SQL Query
Here is the New ClassClass Users
'private Arr to hold users Objects
Private arr
'0 : Binary ADTG , 1: XML
Public CacheType 'as Byte
' Cache File Path
Public CachePath 'as String
''''' Paging Variables
Private mPageCount 'as Long
Public PageIndex 'as Long
Public PageSize 'as Long
'for getting top records if larget than 0
Public TopRecords 'as Long
'destroy arr
Private Sub Class_Terminate()
Erase arr
end Sub
Private Sub Class_Initialize()
Initialize
TopRecords = 0
PageIndex = 0
PageSize = 0
CacheType = 0
CachePath = ""
end Sub
'Initialize collection
Public Sub Initialize()
redim Arr(-1)
mPageCount = 0
End Sub
'return Upper Bound of objects array
Public Property get UpperBound()
UpperBound = UBound(arr)
End Property
'return object at index i
Public Property Get ElementAt(i)
set ElementAt = arr(i)
End Property
'return Upper Bound of objects array
Public Property Get PageCount()
PageCount = mPageCount
End Property
'Select users by Status ( u can add more search parameters or create another selectUsers method)
Public Sub SelectUsers(Status)
Initialize
Dim rs : Set rs = server.CreateObject("ADODB.Recordset")
'Do paging
If PageSize>0 And PageIndex>0 Then
RS.CursorLocation = adUseClient
RS.CacheSize = PageSize
RS.PageSize = PageSize
End If
'if open from XML
If CachePath>"" Then
If Status>0 Then rs.Filter = "Status="& Status
Rs.Open CachePath,,,,adCmdFile
'if Open from DB
Else
If TopRecords>0 Then
rs.MaxRecords = TopRecords
Top = " top "& TopRecords &" "
End If
Dim SQL : SQL= "SELECT "& Top &" * From users"
If Status>0 Then SQL = SQL & " where Status="& Status
rs.Open SQL , ConnStr, adOpenForwardOnly,adLockReadOnly,adCmdText
End If
' if paging : get page count
If Not rs.EOF And PageSize>0 And PageIndex>0 Then
RS.AbsolutePage = PageIndex
mPageCount = RS.PageCount
End If
Dim i : i=0
'(TopRecords=0 Or i<TopRecords) condition is needed to get correct Top N records when opening from Cache
' ,MaxRecords doesn't seem to work in that case
While Not rs.EOF And (TopRecords=0 Or i<TopRecords) And (PageSize=0 Or i<PageSize)
'loop until EOF or Paging Size reached
'create Object and set Values then add to collection
Dim u : Set u = New User
u.ID = CLng(Rs("ID"))
u.Name = Rs("Name")
u.Email = Rs("Email")
u.Password = Rs("Password")
u.LastLogin = cdate(rs("LastLogin"))
u.Status = cbyte(Rs("Status"))
ReDim Preserve arr(i)
Set arr(i) = u
set u = Nothing
rs.MoveNext
i=i+1
Wend
rs.Close
Set rs = Nothing
End Sub
' Open Recordset and Save it
Public Sub CacheUsers(Status)
If CachePath="" Then Exit Sub
Dim rs : Set rs = server.CreateObject("ADODB.Recordset")
Dim Top
If TopRecords>0 Then
rs.MaxRecords = TopRecords
Top = " top "& TopRecords &" "
End if
Dim SQL : SQL= "SELECT "& Top &" * From users"
If Status>0 Then SQL = SQL & " where Status="& Status
rs.Open SQL , ConnStr, adOpenForwardOnly,adLockReadOnly,adCmdText
Call SaveRS(rs)
rs.Close
Set rs = Nothing
End Sub
'Handle saving Recordset to Stream
Private Sub SaveRS(rs)
Const adTypeText = 2
Const adTypeBinary = 1
Const adSaveCreateOverWrite = 2
Const adPersistXML = 1
Const adPersistADTG = 0
Dim Stream : Set Stream = Server.CreateObject("ADODB.Stream")
If CacheType=1 Or CacheType=2 Then
Stream.Type = adTypeText
Stream.Open
rs.Save Stream, adPersistXML
Else
Stream.Type = adTypeBinary
Stream.Open
rs.Save Stream, adPersistADTG
end If
Application.Lock
Stream.SaveToFile CachePath,adSaveCreateOverWrite
Stream.Close
Application.UnLock
Set Stream = Nothing
End Sub
End Class
Sample Usage
1- Save Cache when Target Data is updatedDim aUsers : Set aUsers = New Users
aUsers.CacheType = 1 'XML cache
aUsers.CachePath = Server.MapPath("users.xml")
aUsers.TopRecords = Top
Call aUsers.CacheUsers(0)
Set aUsers = Nothing
2- Read CacheDim aUsers : Set aUsers = New Users
aUsers.CacheType = 1 'XML cache
aUsers.CachePath = Server.MapPath("users.xml")
aUsers.TopRecords = Top
Call aUsers.SelectUsers(0)
Dim i,aUser
For i=0 To aUsers.UpperBound
Set aUser = aUsers.ElementAt(i)
'do something
Set aUser = Nothing
Next
Set aUsers = Nothing
Time Comparison
I did a timing test to compare between reading from a SQL Server and from XML/Binary file, but Reading from a local SQL Server (on the same machine as IIS) will be always faster, so to get real world results i simulated a LAN/WAN Connection on SQL Server [How to? check: Simulating WAN Connection for SQL Server Performance Testing].
And to simulate concurrent connections on IIS and calculate timing i used the very neat [Microsoft Web Stress Tool] on a PC of P3.2GHZ, 1GB Ram, Windows XP, SQL Server 2005 Express.
| method / Records | 100 | 500 | 1000 | 3000 |
|---|---|---|---|---|
| Open WAN SQL Server | 559.25 | 614.80 | 980.85 | 2489.85 |
| Open LAN SQL Server | 276.70 | 436.00 | 777.60 | 2368.90 |
| Open XML Cache | 72.25 | 369.95 | 821.10 | 2469.31 |
| Open Binary Cache | 60.45 | 311.25 | 666.45 | 2062.20 |
As you can see reading less than 1000 records from XML Cache is slightly faster than reading from LAN SQL Server and time saving gets bigger when compared to WAN SQL server(reading >1000 records gets slower in XML due to loading&parsing overhead).
You can also notice that reading from Binary(ADTG) is faster than reading from XML and LAN/WAN SQL Server in all cases, and files created in binary are Approx. 50% smaller than the equivalent XML files.
I needed to do some testing on SQL Server on my PC, but response was of course very fast, so i needed to simulate WAN connection to get some real world results..
i found a good article [How To: Simulate WAN Connections in Your Own Test Lab, For Free!] by Michel Roth
Since WAN main characteristics are : limited in bandwidth and high in latency, we can use one of two software:
1- NetLimiter Pro (commercial-28-day evaluation period)
it can limit the bandwidth per process. You can use this ability in two ways:
a. You can install it on the Server (ex:SQL Server) and limit the incoming bandwidth.
b. You can install it on the Client machine(ex:IIS) and limit the outgoing bandwidth for the client(Preferred).
2. TMnetSim Network Simulator (free!)
a *free* tool to allow you to simulate network latency & packet loss!,You can install it on the client or on the server. I gonna use the tool on the Server.
Here are the steps to setup that tool for testing SQL Server 2005 Express on your local PC:
- Download zip file and run executable
- SQL Server default port is 1433 so:
Under 'Outbound Connection' >> enter IP: 127.0.0.1, Port : 1433
Under 'Inbound Connection' >> enter port: 1422 (any other port!)
Under 'inbound-->outbound policies' >> Delay Type : Gausian, Delay Base:250 , Daley Jitter : 25 - click "Start"
- Setup your Connection String on ASP
Provider=SQLOLEDB; Data Source=127.0.0.1\sqlexpress,1422;Initial Catalog=db;User Id=user;Password=pass;
or ASP.Net :<connectionStrings>
<add name="SQLConn" connectionString="Data Source=127.0.0.1\sqlexpress,1422;Initial Catalog=DB;Persist Security Info=False;User ID=user;Password=pass;Network Library=dbmssocn" providerName="System.Data.SqlClient"/>
</connectionStrings> - If things running ok, you should see connection information (Latency- Bandwidth- Packet Loss)

Data Access layer(DAL) is is a layer of code which provides simplified access to data stored in persistent storage of some kind (DBMS, XML,..)
DAL would return a reference to an object [in terms of object-oriented programming(OOP)] with its attributes instead of a recordset of fields from a database table. This allows presentation layer to be created with a higher level of abstraction, and make project database independent.
So, instead of using SQL commands such as insert, delete, and update to access a specific table in a database, a method would be called from inside the class, which would return an object containing the requested values. instead of making a query into a database to fetch all users from several tables the application can call a single method from a DAL which encapsulate those database calls.
As an example of DAL, we'll use a simple Table of 'Users', here is the SQL Create script:CREATE TABLE [dbo].[Users](
[ID] [int] IDENTITY(1,1) PRIMARY KEY NOT NULL,
[Name] [varchar](100) NOT NULL DEFAULT (''),
[Email] [varchar](100) NOT NULL DEFAULT (''),
[Password] [varchar](20) NOT NULL DEFAULT (''),
[LastLogin] [smalldatetime] NOT NULL DEFAULT ('1/1/1900'),
[Status] [tinyint] NOT NULL DEFAULT (0)
)
We will create 2 classes for users table, Object Class and Object Collection Class with support of paging
Object Class'few needed ADO Constants, instead of including adovbs.asp
Const adCmdText = &H0001
Const adCmdTableDirect = &H0200
Const adCmdFile = &H0100
Const adOpenForwardOnly = 0
Const adOpenKeyset = 1
Const adLockReadOnly = 1
Const adLockOptimistic = 3
Const adExecuteNoRecords = &H00000080
Const adUseClient = 3
''''''''''''''' Object class '''''''''''''''
Class User
' Object Attributes / Table Fields
Public ID 'as Long
Public Name 'as String
Public Email 'as String
Public Password 'as Strng
Public LastLogin 'as Date
Public Status 'as byte
Private Sub Class_Initialize()
Initialize
End Sub
'default values
Public Sub Initialize()
ID=0
Name=""
Email = ""
Password = ""
LastLogin = Now()
Status=0
End Sub
'Select user by ID
Public Sub SelectUser(UserID)
Initialize
if UserID=0 then exit Sub
Dim rs : Set rs = server.CreateObject("ADODB.Recordset")
Dim SQL : SQL= "SELECT * From users where ID="& UserID
rs.Open SQL, ConnStr ,adOpenForwardOnly, adLockReadOnly,adCmdText
If Not rs.EOF Then
ID = CLng(Rs("ID"))
Name = Rs("Name")
Email = Rs("Email")
Password = Rs("Password")
LastLogin = Cdate(rs("LastLogin"))
Status = cbyte(Rs("Status"))
End If
rs.Close
Set rs = Nothing
End Sub
'Insert New User and get new ID
Public Sub InsertUser()
if ID<>0 then exit Sub
Dim rs : Set rs = server.CreateObject("ADODB.Recordset")
rs.Open "Users", ConnStr ,adOpenKeyset,adLockOptimistic,adCmdTableDirect
rs.AddNew
Rs("Name") = Name
Rs("Email") = Email
Rs("Password") = Password
rs("LastLogin") = LastLogin
Rs("Status") = Status
rs.update
ID = CLng(Rs("ID"))
Rs.Close
Set rs = Nothing
End Sub
'Update User
Public Sub UpdateUser()
if ID=0 then exit Sub
Dim rs : Set rs = server.CreateObject("ADODB.Recordset")
Dim SQL : SQL= "SELECT * From users where ID="& ID
rs.Open SQL, ConnStr ,adOpenForwardOnly,adLockOptimistic,adCmdText
If Not rs.EOF then
Rs("Name") = Name
Rs("Email") = Email
Rs("Password") = Password
rs("LastLogin") = LastLogin
Rs("Status") = Status
rs.update
End If
Rs.Close
Set rs = Nothing
End Sub
'delete user
Public Sub DeleteUser()
if ID=0 then exit Sub
Dim conn : Set conn = server.CreateObject("ADODB.Connection")
conn.Execute "Delete From users where ID="& ID, ,adExecuteNoRecords
Conn.Close
Set Conn = Nothing
Initialize
End Sub
End Class
Object Collection ClassClass Users
'private Array to hold users Objects
Private arr
''''' Paging Variables
Private mPageCount 'as Long
Public PageIndex 'as Long
Public PageSize 'as Long
'for getting top records if larger than 0
Public TopRecords 'as Long
'destroy arr
Private Sub Class_Terminate()
Erase arr
end Sub
Private Sub Class_Initialize()
Initialize
TopRecords = 0
PageIndex = 0
PageSize = 0
end Sub
'Initialize collection
Public Sub Initialize()
redim Arr(-1)
mPageCount = 0
End Sub
'return Upper Bound of objects array
Public Property get UpperBound()
UpperBound = UBound(arr)
End Property
'return object at index i
Public Property Get ElementAt(i)
set ElementAt = arr(i)
End Property
Public Property Get PageCount()
PageCount = mPageCount
End Property
'Select users by Status (you can add more search parameters or create another select method)
Public Sub SelectUsers(Status)
Initialize
Dim rs : Set rs = server.CreateObject("ADODB.Recordset")
'Do paging
If PageSize>0 And PageIndex>0 Then
RS.CursorLocation = adUseClient
RS.CacheSize = PageSize
RS.PageSize = PageSize
End if
If TopRecords>0 Then rs.MaxRecords = TopRecords
Dim Top
If TopRecords>0 Then Top = " top "& TopRecords &" "
Dim SQL : SQL= "SELECT "& Top &" * From users"
If Status>0 Then SQL = SQL & " where Status="& Status
rs.Open SQL, ConnStr, adOpenForwardOnly,adLockReadOnly,adCmdText
' if paging : move to PageIndex and get page count
If Not rs.EOF And PageSize>0 And PageIndex>0 Then
RS.AbsolutePage = PageIndex
mPageCount = RS.PageCount
End If
Dim i : i=0
'loop until EOF or Paging Size reached
while Not rs.EOF And (PageSize=0 Or i<PageSize)
'create Object and set Values then add to collection
Dim u : Set u = New User
u.ID = CLng(Rs("ID"))
u.Name = Rs("Name")
u.Email = Rs("Email")
u.Password = Rs("Password")
u.LastLogin = cdate(rs("LastLogin"))
u.Status = cbyte(Rs("Status"))
ReDim Preserve arr(i)
Set arr(i) = u
set u = Nothing
rs.MoveNext
i=i+1
Wend
rs.Close
Set rs = Nothing
End Sub
End Class
Sample UsageDim Usrs : Set Usrs = New Users
Usrs.PageIndex = 1
Usrs.PageSize = 10
Call Usrs.SelectUsers(0)
Dim i,Usr
For i=0 To Usrs.UpperBound
Set Usr = Usrs.ElementAt(i)
'Do something
Set Usr = Nothing
Next
Set Usrs = Nothing
Note that the paging method used in the collection class is the usual ADO method,since it is not the optimal or the fastest,Please check my article on [Paging in ASP and ASP.Net] for better paging methods..
This Data access layer can be later extended to be also a Business Logic Layer(BLL) by enforcing business rules that may apply (permissions, validations, formatting..)
Also if you have a lot of DAL classes, DAL can be moved to ActiveX DLL (Com Object) to improve performance..[25+ ASP Tips to Improve Performance and Style]
This article Shows & compares many paging methods of ASP and ASP.Net and also SQL Paging methods that can be used in both of them..
You would notice here that all the fast paging methods runs a small query first to get number of records and calculate page count..
Classic ASP
in ASP , The article [How do I page through a recordset?] is a very good refrence. and I think that paging by Recordset.Move() was the best of paging methods that don't use stored procedures.
Even Recordset.GetRows() + Recordset.Move() performed best -as the article says- which combines the effective move() technique, with GetRows(). but for me since I use Custom Business Classes ,GetRows() is not needed.
GetRows() converts a heavy recordset object into a lighter-weight array for local processing
Recordset.Move()
The Recordset.Move() technique uses Move() method to skip the first n rows in the result set to start at the first row for the page we are interested in.
<!--#include file=inc.asp-->
<!--#include file=topRS.asp-->
<%
rstart = PerPage * Pagenum - (PerPage - 1)
dataSQL = "SELECT ArtistName, Title FROM SampleCDs WITH (NOLOCK)"
set rs = conn.execute(dataSQL)
if not rs.eof then
rs.move(rstart-1)
response.write "<table border=0 cellpadding=5>"
for x = 1 to PerPage
if rs.eof then exit for
artist = rs(0)
title = rs(1)
rs.movenext
next
response.write "</table>"
else
response.write "No rows found."
response.end
end if
%>
<!--#include file=foot.asp-->ASP.Net
In ASP.Net you would hear about DataGrid but it is terrible cause each time you move another page the entire records are fetched..
Now lets examine good paging methods for ASP.Net:
1) Dataset
Using the method DbDataAdapter.Fill(DataSet, Int32, Int32, String) which specifies the start record and number of records to fill the DataSet with.
'Count Query
Cmd.CommandText = "Select count(ID) from Users where.."
Count = CInt(Cmd.ExecuteScalar())
PageCount = Math.Ceiling(Count / PageSize)
'Select Query
cmd.CommandText = "Select Top " & (PageIndex * PageSize) & " * from Users where.. Order By.."
Dim DS As New DataSet
Dim DA As DbDataAdapter = PFactory.CreateDataAdapter()
DA.SelectCommand = Cmd
DA.Fill(DS, (PageIndex - 1) * PageSize, PageSize, SrcTable)
Dim DT As New DataTable= DS.Tables(SrcTable)
DA = Nothing
DS = nothing
For Each Row As DataRow In DT.Rows
'Do Something
Next
DT = NothingHowever, MSDN @ [ADO.Net & Paging Through a Query Result ] Says:
This might not be the best choice for paging through large query results because,although the DataAdapter fills the target DataTable or DataSet with only the requested records, the resources to return the entire query are still used ..
Remember that the database server returns the entire query results even though only one page of records is added to the DataSet.
That is true, At first testing the time results for this method was bad even for closer pages..
To tweak this: I added a top clause in the select query to select Top(PageSize * PageIndex), so if we are requesting the 2nd page of a page size of 10 the database returns only 20 records, and the DataSet is filled with the 2nd 10 records.. instead of letting the database to return the entire table (1 million records). this tweak which i used for DataSet,DataReader and Recordset make their performance+time is much better for closer pages.
2) DataReader
You won't see this out there very often! it is inspired By ADO Paging method[Recordset.Move()]. check it out:
'Count Query
Cmd.CommandText = "Select count(ID) from Users where.."
Count = CInt(Cmd.ExecuteScalar())
PageCount = Math.Ceiling(Count / PageSize)
'Select Query
cmd.CommandText = "Select Top " & (PageIndex * PageSize) & " * from Users where.. Order By.."
Dim Reader As DbDataReader =Cmd.ExecuteReader(CommandBehavior.CloseConnection)
'> Move to desired Record
Dim startRecord as Integer = (PageIndex - 1) * PageSize
For i As Integer=0 To (startRecord - 1)
If Not Reader.Read Then Exit For
Next
While Reader.Read()
'Do Something
End While
Reader.Close()
Reader = NothingWhen it is only one table you query or it is read only & forward only mode : DataReader is better.
and According to tests @ [A Speed Freak's Guide to Retrieving Data in ADO.NET] DataReader will be noticeably faster in a larger Page Size
3) Recordset
What? yes, why not.. lets try ADO Recordset in ASP.Net, just add reference to Microsoft ActiveX Data Objects 2.5+ ,here is the code:
Dim Conn As New ADODB.Connection
Dim Cmd As New ADODB.Command
Conn.Open(ConnString)
Cmd.ActiveConnection = Conn
Dim RS As New ADODB.Recordset
Dim i As Integer = 0, Count As Integer = 0
'Count Query
cmd.CommandText = "Select count(ID) from Users where.."
Rs.Open(Cmd, , CursorTypeEnum.adOpenForwardOnly, LockTypeEnum.adLockReadOnly,CommandTypeEnum.adCmdText)
If Not Rs.EOF Then Count = CInt( Rs(0).Value)
Rs.Close()
PageCount = Math.Ceiling(Count / PageSize)
'Select Query
cmd.CommandText = "Select Top " & (PageIndex * PageSize) & " * from Users where.. Order By.."
Rs.MaxRecords = PageIndex * PageSize
RS.Open(Cmd, , CursorTypeEnum.adOpenForwardOnly, LockTypeEnum.adLockReadOnly,CommandTypeEnum.adCmdText)
If Not RS.EOF Then RS.Move((PageIndex - 1) * PageSize)
While not RS.EOF()
'Do Something
Rs.MoveNext()
Wend
RS.Close()
RS = Nothing
Conn.Close()SQL Paging
The following 2 methods will use SQL Paging which can be used in Both ASP and ASP.Net...
4) Top Clause
A SQL Paging using Top Clause as described in MSDN [How To: Page Records in .NET Applications] , and Reading records using a DataReader.
'Count Query
Cmd.CommandText = "Select Count(ID) from Users where.."
Count = CInt(Cmd.ExecuteScalar())
PageCount = Math.Ceiling(Count / PageSize)
'Select Query
Cmd.CommandText= "Select * from (" & _
"Select Top " & PageSize & " * from (" & _
"Select Top " & (PageIndex * PageSize) & " * from Users as T1 where.." &_
" Order by ID asc " & _
") as T2 Order by ID desc " & _
") as T3 Order by ID asc "
Dim Reader As DbDataReader = Cmd.ExecuteReader(CommandBehavior.CloseConnection)
'Last page fix!!
If PageSize>1 and PageCount>1 and PageIndex=PageCount And (Count Mod PageSize<>0) Then
For i =1 To PageSize - (Count Mod PageSize)
If Not Reader.Read Then Exit For
Next
End If
'Loop the desired records
while Reader.Read()
'do Something
end While
Reader.Close()
Reader = NothingNote that: If you are at last page and number of records their is less than PageSize, This method will always return the last PageSize records.. so you would need to skip some records first to reach the desired records..
5) Row_Number Function
A SQL Paging method described in [Custom Paging in ASP.NET 2.0 with SQL Server 2005], Row_Number is a new method introduced in SQL Server 2005, which enables us to associate a sequentially-increasing row number for the results returned.
'Count Query
Cmd.CommandText = "Select count(ID) from Users where.."
Count = CInt(Cmd.ExecuteScalar())
PageCount = Math.Ceiling(Count / PageSize)
'Select Query
Cmd.CommandText="Select Top " & PageSize & " * from (" & _
"Select *,ROW_NUMBER() OVER (ORDER BY ID ASC) AS Row from Users where.."
" ) as T1 where Row>" & ((PageIndex - 1) * PageSize) & " and Row<=" & (PageIndex * PageSize)
Dim Reader As DbDataReader= Cmd.ExecuteReader(CommandBehavior.CloseConnection)
while Reader.Read()
'do Something
end While
Reader.Close()
Reader = NothingInteresting results on Timing these Paging methods
I Timed these paging methods on one table of more than 1 million records , getting & looping 100 records per page , and page index moves further to get records as shown in the table.
Tests made using [Microsoft Web Stress Tool] on a PC of P3.2GHZ, 1GB Ram, Windows XP, SQL Server 2005 Express.

| Method | 1 to 10000 | 10000 to 100000 | 100000 to 500000 | 500000 to 1000000 |
|---|---|---|---|---|
| Recordset | 601.00 | 1101.06 | 2708.94 | 4750.35 |
| DataSet | 518.79 | 734.94 | 2264.78 | 4463.53 |
| DataReader | 490.12 | 813.29 | 2165.71 | 4094.18 |
| Top Clause | 518.88 | 735.29 | 1881.18 | 4017.88 |
| Row_Number | 381.18 | 466.35 | 801.18 | 1309.76 |
* Generally, when the SQL query sort or search records by indexed columns: query cost is lower..
Update!: I wrote a VB.Net Class to encapsulate all that Data Access code to help me [Write Less & Generic Data Access Code in ADO.NET 2.0], check it out!

Web developer, jQuery plugin author, social media fan and Technology Blogger.