Implement Custom Paging in GridView With LINQ

In this article we will take a look at how to do pagination in a GridView control.

Introduction

Paging is very helpful for presenting a huge amount of data in the page because this helps speeding up the loading performance of the page and provides a friendlier UI to end users in terms of data presentation. For this example, I will highlight how to implement custom paging in a GridView control using the power of LINQ and will show you some tips to maximize the performance of a paged grid.

For those who are not familiar with LINQ, here's a short overview. Language-Integrated Query (LINQ) is a set of features introduced in .NET Framework 3.5 that extends powerful query capabilities to the language syntax of C# and Visual Basic. LINQ introduces standard, easily-learned patterns for querying and updating data and the technology can be extended to support potentially any kind of data store. For more details please read the official documentation here.

To get started let's go ahead and fire up Visual Studio and then select new web application / website project. Add a new page and then set up your page by adding a GridView and a Repeater control. The HTML markup should look something like this:

  1. <h2>GridView Custom Paging with LINQ</h2>  
  2.   
  3. <asp:GridView ID="grdCustomer" runat="server" AutoGenerateColumns="false">  
  4.      <Columns>  
  5.          <asp:BoundField DataField="Company" HeaderText="Company" />  
  6.          <asp:BoundField DataField="Name" HeaderText="Name" />  
  7.          <asp:BoundField DataField="Title" HeaderText="Title" />  
  8.          <asp:BoundField DataField="Address" HeaderText="Address" />  
  9.      </Columns>  
  10. </asp:GridView>  
  11. <asp:Repeater ID="rptPager" runat="server">  
  12.      <ItemTemplate>  
  13.          <asp:LinkButton ID="lnkPage" runat="server"   
  14.                         Text='<%#Eval("Text") %>'   
  15.                         CommandArgument='<%#Eval("Value") %>'   
  16.                         Enabled='<%#Eval("Enabled") %>'   
  17.                         OnClick="Page_Changed"   
  18.                         ForeColor="#267CB2"   
  19.                         Font-Bold="true" />  
  20.      </ItemTemplate>  
  21. </asp:Repeater>  
The HTML mark-up above consists of a GridView and Repeater data representation controls. The GridView is where we display the list of customer information from the database. The Repeater will serve as our custom pager.

Keep in mind that in this example I used Northwind.mdf as my database that you can download that from here and I used Entity Framework so that I can work on the conceptual model. I will not elaborate more about the details on how to pull data from a database using EF. If you are new to Entity Framework then you can have a look at my previous article that outlined the details of EF:

  1. Entity Framework – Inserting Data to Database
  2. Entity Framework – Fetching and Populating the data in the Form
  3. Entity Framework – Editing, Updating and Deleting data in the Form

Here's the code behind for the entire stuff:

  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Web.UI.WebControls;  
  5.   
  6. namespace WebFormsDemo  
  7. {  
  8.   
  9.     public class Customer  
  10.     {  
  11.         public string Company { getset; }  
  12.         public string Name { getset; }  
  13.         public string Title { getset; }  
  14.         public string Address { getset; }  
  15.     }  
  16.   
  17.     public partial class GridViewPagingWithLINQ : System.Web.UI.Page  
  18.     {  
  19.        
  20.         private DB.NORTHWNDEntities northWindDB = new DB.NORTHWNDEntities();  
  21.   
  22.         private List<Customer> GetCustomerEntity() 
  23.         {  
  24.             var customer = from c in northWindDB.Customers  
  25.                            select new Customer {  
  26.                                Company = c.CompanyName,  
  27.                                Name = c.ContactName,  
  28.                                Title = c.ContactTitle,  
  29.                                Address = c.Address  
  30.                            };  
  31.   
  32.             return customer.ToList();  
  33.         }  
  34.   
  35.         private void BindCustomerListGrid(int pageIndex) 
  36.         {  
  37.   
  38.             int totalRecords = GetCustomerEntity().Count;  
  39.             int pageSize = 10;  
  40.             int startRow = pageIndex * pageSize;  
  41.   
  42.             grdCustomer.DataSource = GetCustomerEntity().Skip(startRow).Take(pageSize);  
  43.             grdCustomer.DataBind();  
  44.   
  45.             BindPager(totalRecords, pageIndex, pageSize);  
  46.   
  47.         }  
  48.   
  49.         private void BindPager(int totalRecordCount, int currentPageIndex, int pageSize) 
  50.         {  
  51.             double getPageCount = (double)((decimal)totalRecordCount / (decimal)pageSize);  
  52.             int pageCount = (int)Math.Ceiling(getPageCount);  
  53.             List<ListItem> pages = new List<ListItem>();  
  54.             if (pageCount > 1)
  55.             {  
  56.                 pages.Add(new ListItem("FIRST""1", currentPageIndex > 1));  
  57.                 for (int i = 1; i <= pageCount; i++) 
  58.                 {  
  59.                     pages.Add(new ListItem(i.ToString(), i.ToString(), i != currentPageIndex + 1));  
  60.                 }  
  61.                 pages.Add(new ListItem("LAST", pageCount.ToString(), currentPageIndex < pageCount - 1));  
  62.             }  
  63.   
  64.             rptPager.DataSource = pages;  
  65.             rptPager.DataBind();  
  66.         }  
  67.   
  68.         protected void Page_Changed(object sender, EventArgs e) 
  69.         {  
  70.             int pageIndex = Convert.ToInt32(((sender as LinkButton).CommandArgument));  
  71.             BindCustomerListGrid(pageIndex - 1);  
  72.         }  
  73.   
  74.         protected void Page_Load(object sender, EventArgs e) 
  75.         {  
  76.             if (!IsPostBack) {  
  77.                 BindCustomerListGrid(0);  
  78.             }  
  79.         }  
  80.     }  
  81. }  
The GetCustomerEntity() method is where we queried the data from the database using the LINQ syntax. The method returns a List of Customers. The BindCustomerListGrid() is the method in which we bind the data to the GridView. You will see in that method that it uses the Take() and Skip() LINQ operators to chunk the data based on the page size that we passed in. The good thing about these operators is that it allows you to skip a certain number of rows and only take a limited number of rows from that point. For example we set the page size to 10 which means that it will only select and display 10 records per page instead of selecting the entire results from the database that the SqlDataSource is doing by default. The BindPager() method is where we construct our pager based on the totalRecords and pageSize. The Page_Changed event handles the paging that basically sets what page the grid should display.

Running the code above will show something as in the following:

On initial load



After paging



Using firebug, you can see the number of milliseconds the page was rendered on initial page load and after paging. Now let's improve the BindCustomerListGrid() method to speed up more our paging functionality using Application variable and Caching. Here's the modified method below:

  1. private void BindCustomerListGrid(int pageIndex)   
  2. {  
  3.     int totalRecords = 0;  
  4.     int pageSize = 10;  
  5.     int startRow = pageIndex * pageSize;  
  6.   
  7.     if (Convert.ToInt32(Application["RowCount"]) == 0)
  8.     {  
  9.         totalRecords = GetCustomerEntity().Count();  
  10.         Application["RowCount"] = totalRecords;  
  11.     }  
  12.     else 
  13.     {  
  14.         totalRecords = Convert.ToInt32(Application["RowCount"]);  
  15.     }  
  16.   
  17.     List<Customer> customerList = new List<Customer>();  
  18.   
  19.     if (Cache["CustomerList"] != null
  20.     {  
  21.         customerList = (List<Customer>)Cache["CustomerList"];  
  22.     }  
  23.     else    
  24.     {  
  25.         customerList = GetCustomerEntity();  
  26.         Cache.Insert("CustomerList", customerList, null, DateTime.Now.AddMinutes(3), TimeSpan.Zero);  
  27.     }  
  28.   
  29.     grdCustomer.DataSource = customerList.Skip(startRow).Take(pageSize);  
  30.     grdCustomer.DataBind();  
  31.     BindPager(totalRecords, pageIndex, pageSize);  
  32.  }  
As you can see, we store the value of totalRecords in an Application variable and use that on subsequent request. This will minimize database calls because we will not be calling GetCustomerEntity().Count() anymore and instead use the value stored in the Application variable. Another change we made is that we store the result set in a cache so that again we will not be hitting the database on each subsequent request. You should set the value of the Application variable to 0 and set the Cache to null in the event where you do an update, insert or delete to refresh the values.

The following show the output when running the code.

On initial load



After paging



As you see, there's a big change on the performance of page loading time and on subsequent requests. That's it. I hope someone finds this article useful!