Introduction
Pagination is a typical process in data display whereby large sets of data are broken
into discrete sections for viewing, more often than not used in conjunction with some type of grid or list component. In
Part 1 and
Part 2 of this series, we walked through the process
of dynamic sorting using LINQ to return a sorted collection of objects. In this tip, we'll
walk through the basics of how to add pagination into the mix.
Using the Code
In this tip, we're going to use the basic data model that was defined as a structure
in the previous tip:
Public Structure Person
Public Property FirstName() As String
Public Property MiddleInitial() As String
Public Property LastName() As String
Public Overrides Function ToString() As String
Return String.Format("{0}, {1} {2}", LastName, FirstName, MiddleInitial)
End Function
End Structure
Next, we'll set up the data store containing a list of these objects:
Public Class PeopleDataStore
Public Const SortAscending As Boolean = True
Public Const SortDescending As Boolean = False
Private m_people As List(Of Person) = Nothing
Public Sub New()
m_people = New List(Of Person) From {
New Person() With { .FirstName = "John", .MiddleInitial = "Q", .LastName = "Doe" },
New Person() With { .FirstName = "Jane", .MiddleInitial = Nothing, .LastName = "Appleton" },
New Person() With { .FirstName = "Jim", .MiddleInitial = "H", .LastName = "Smith" },
New Person() With { .FirstName = "Joe", .MiddleInitial = Nothing, .LastName = "Plumber" }
}
End Sub
Public ReadOnly Property People() As List(Of Person)
Get
Return m_people
End Get
End Property
Public Function GetPeople(Optional ByVal sortPropertyName As String = Nothing, Optional ByVal sortAscending As Boolean = True, Optional ByVal pageIndex As Integer = 0, Optional ByVal pageSize As Integer = 25) As List(Of Person)
Dim actualIndex As Integer = (pageIndex * pageSize)
Dim list As List(Of Person) = m_people
If (String.IsNullOrEmpty(sortPropertyName) = False) Then
Dim personType As Type = GetType(Person)
If (personType.GetProperties().Any(Function(prop) prop.Name = sortPropertyName AndAlso prop.CanRead)) Then
Dim pinfo As PropertyInfo = personType.GetProperty(sortPropertyName)
Dim paramExpr As ParameterExpression = Expression.Parameter(GetType(Person), "instance")
Dim memberExpr As MemberExpression = Expression.Property(paramExpr, pinfo)
Dim orderByFunc As Func(Of Person, Object) = Expression.Lambda(Of Func(Of Person, Object))(memberExpr, paramExpr).Compile()
Dim sortFunc As Func(Of IEnumerable(Of Person), IOrderedEnumerable(Of Person)) = Nothing
If (sortAscending) Then
sortFunc = (Function(source) source.OrderBy(orderByFunc))
Else
sortFunc = (Function(source) source.OrderByDescending(orderByFunc))
End If
list = sortFunc(list).ToList()
End If
End If
If (actualIndex <= (list.Count + pageSize)) Then
Return list.Skip(actualIndex).Take(pageSize).ToList()
Else
Return list
End If
End Function
End Class
Taking a walk through the code, we start out by defining a couple of Boolean
constants
for better legibility when calling the GetPeople
method; either these constants or literal
Boolean values can be used for the sortAscending
parameter on the method. Next,
we create a strongly-typed list of the Person
type to hold our data values, then populate
the list in the constructor; providing a corollary read-only property of the same type for access
to the list.
The GetPeople
method is defined with four optional parameters: the name of the public property from
the Person
structure used for sorting, a Boolean flag indicating whether the list should be
sorted in ascending or descending order, an integer value for the starting index of the paging operation,
and an integer value for the size of the page collected from the paging operation. The paging values are determined
by the pageIndex
and pageSize
parameters: the pageIndex
parameter defines
where in the collection the paging operation should start while the pageSize
parameter defines the
number of elements to be retrieved from the collection. Since the code will not have any notion of where to
begin taking elements from the collection, the actualIndex
variable is defined based on creating an index
which is the product of the number of elements multiplied by the requested page index.
If no values have been supplied for any of the parameters, the method simply returns the list values in their
default order. If the sortPropertyName
parameter is supplied, we then check to see if the
Person
type defines a readable public property of the same name and continue processing should this
prove true. From there, we obtain a PropertyInfo
instance from the Person
type
to be used in the sorting operation. At this point, the code becomes divergent from the prior tips in that we
begin constructing a dynamic lambda expression
as the delegate parameter for the OrderBy
or OrderByDescending
extension methods; the first
portion of the expression is a ParameterExpression
instance which creates the main parameter for the
lambda expression. Next, we create a MemberExpression
instance which identifies the desired property
from the parameter value. The orderByFunc
variable is populated by the compiled result of
the lambda expression assembly call.
Next, we create another Func delegate to
define which sorting method to call based on the sortAscending
parameter supplied to the method call. If
no value is supplied for the parameter, collection sorting defaults to sort ascending. Since we know that the
OrderBy
and OrderByDescending
methods are extensions supplied to the
IEnumerable(T) interface and our list implements
this interface, we define the Func delegate to
access an input parameter of List(Of Person)
. The output parameter is then defined as the same
return type as defined by the OrderBy
and OrderByDescending
methods, this being an instance
of IOrderedEnumerable(T). With the
delegate defined, we then apply sorting to the list.
Finally, we perform a sanity check on the paging values supplied against the number of elements in the source list. If the list
contains fewer elements than is requested by the paging values, the entire list is returned. If the list contains more element than
the paging values, the result of applying the Skip
and Take
extension methods is returned.
Module Program
Public Shared Sub Main()
Dim consoleAction As Action(Of Person) = (Function(person) Console.WriteLine(Person))
Dim dataStore As PeopleDataStore = New PeopleDataStore()
Console.WriteLine("People listed in default order:")
dataStore.People.ForEach(AddressOf consoleAction)
Console.WriteLine("{0}People listed in reverse alphabetical order by last name:", Environment.NewLine)
dataStore.GetPeople("LastName", PeopleDataStore.SortDescending, 0, 2).ForEach(AddressOf consoleAction)
End Sub
End Module
Further Reading