SQL Server – OpenXML

Posted by Jack Altiere on April 14th, 2008

It suprises me when I run across developers that have never used (or even heard of) the OpenXML function in SQL Server.  It’s a very handy tool to have in the bag of tricks, and we’re going to walk through an example using this method. 

First, some background.  OpenXML was introduced in SQL Server 2000.  It allows you to use an XML document to pass data into a stored procedure.  This document will be parsed by SQL Server and used just like a table in memory.  OpenXML is great for doing anything in a batch format, such as syncing data between systems or doing bulk operations from disconnected systems.  The key that makes this technique so valuable is that you can perform these operations with a single database call, rather than a lot of separate calls.

In our example, we’re going to use the sample database that everyone knows and loves…..AdventureWorks.  If you don’t have this database available, you can get it here.  For our example, I’m going to pass in an XML document with a list of departments, and I’m going to return a list of contact information for any employee currently working in one of those departments.   If it’s been a while since you’ve seen the AdventureWorks database, here are the tables that we need to be concerned with. (and how they are related)

I’m going to create a simple XML document to represent the departments that we are interested in, it will look like this:

   1: <Departments>
   2:     <Department Name="Engineering" />
   3:     <Department Name="Purchasing" />
   4:     <Department Name="Human Resources" />
   5:     <Department Name="Finance" />
   6: </Departments>

The stored procedure that uses the OpenXML method looks like this:

   1: CREATE PROCEDURE psEmployeesByDepartment
   2:     @XML NText
   3: AS
   4: BEGIN
   5:     SET NOCOUNT ON;
   6:  
   7:     -- This will be the handle to the doc in memory.
   8:     DECLARE @iDoc Int
   9:  
  10:     -- Prepare the document and get a handle to it.
  11:     EXEC sp_xml_preparedocument @iDoc OUTPUT, @XML
  12:  
  13:     -- Get the contact info for the current employees.
  14:     SELECT cont.Title, cont.FirstName, cont.MiddleName, 
  15:     cont.LastName, cont.EmailAddress, input.[Name], cont.Phone
  16:     FROM 
  17:     OPENXML(@iDoc, '/Departments/Department')
  18:         WITH (Name Varchar(128)) AS input
  19:     INNER JOIN HumanResources.Department AS dept 
  20:         ON dept.[Name] = input.[Name]
  21:     INNER JOIN HumanResources.EmployeeDepartmentHistory AS hist
  22:         ON hist.DepartmentID = dept.DepartmentID
  23:     INNER JOIN HumanResources.Employee AS emp
  24:         ON emp.EmployeeID = hist.EmployeeID
  25:     INNER JOIN Person.Contact AS cont
  26:         ON cont.ContactID = emp.ContactID
  27:     WHERE hist.EndDate IS NULL
  28:  
  29:     -- Free up the memory used by the XML document.
  30:     EXEC sp_xml_removedocument @iDoc
  31: END
  32: GO

Let’s walk through this procedure and see how it works.  The first thing to point out is that there are two system stored procedures that are necessary for this operation.  The first is sp_xml_preparedocument.  This procedure parses the XML string, and stores it in memory to use.  It takes an output parameter of type Int that represents a handle to the document that will be used in the OpenXML function call.  The second stored procedure used is sp_xml_removedocument.  This procedure frees up the memory used by the XML file. 

I called the OpenXML function on line 17 above.  It takes the handle that was returned to use from the sp_xml_preparedocument procedure as a parameter, along with an XPath statement that describes how to parse the XML document.  The “WITH” statement after the function call is also important, it describes how to parse the XML document.  In our case, we only have a single attribute called Name.  We can use our XML file just like we use any other table in a query.  Notice on line 18 that I give it an alias, and then on line 20 I use the alias in an inner join just like I would any other table.  

Normally we would call this procedure from some sort of data layer in our application, but for the sake of simplicity I’ll just call it from a query in SQL Management Studio.  Since I’m using Management Studio to write my query, I can’t use an NText variable like our procedure is expecting, but a Varchar will work here.

   1: DECLARE @XML Varchar(2000)
   2:  
   3: SET @XML = 
   4:     '<Departments>
   5:         <Department Name="Engineering" />
   6:         <Department Name="Purchasing" />
   7:         <Department Name="Human Resources" />
   8:         <Department Name="Finance" />
   9:     </Departments>'
  10:  
  11: EXEC psEmployeesByDepartment @XML

Running this query yields the results we wanted, a list of contact information for the employees in the departments from our XML file.

The last thing I want to talk about is a performance trick that is worth mentioning.  Although you won’t notice it in this simple contrived example, sometimes performance using OpenXML can suffer for complex queries.  This could be because of large amounts of data, a large XML file, or complex joins / subqueries.  There is a way to speed things up if you run into performance problems.  You can load your XML document into a temp table, and then free the memory used by it right away.  You can then use the temp table for your operations.  Even though you won’t notice any difference in our example, this would be what our stored procedure looks like if we made this change:

   1: CREATE PROCEDURE psEmployeesByDepartment
   2:     @XML NText
   3: AS
   4: BEGIN
   5:     SET NOCOUNT ON;
   6:  
   7:     -- Document handle.
   8:     DECLARE @iDoc Int
   9:  
  10:     -- Prepare the document and get a handle to it.
  11:     EXEC sp_xml_preparedocument @iDoc OUTPUT, @XML
  12:  
  13:     -- Create the temp table.
  14:     CREATE TABLE #Departments([Name] Varchar(128) 
  15:         COLLATE Latin1_General_CS_AS)
  16:  
  17:     -- Load the temp table.
  18:     INSERT INTO #Departments
  19:     SELECT input.[Name] 
  20:     FROM OPENXML(@iDoc, '/Departments/Department')
  21:         WITH ([Name] Varchar(128)) AS input
  22:  
  23:     -- Free up the memory used by the XML document.
  24:     EXEC sp_xml_removedocument @iDoc
  25:  
  26:     -- Get the contact info for the current employees.
  27:     SELECT cont.Title, cont.FirstName, cont.MiddleName, 
  28:     cont.LastName, cont.EmailAddress, tmp.[Name], cont.Phone
  29:     FROM 
  30:     #Departments AS tmp
  31:     INNER JOIN HumanResources.Department AS dept 
  32:         ON dept.[Name] = tmp.[Name]
  33:     INNER JOIN HumanResources.EmployeeDepartmentHistory AS hist
  34:         ON hist.DepartmentID = dept.DepartmentID
  35:     INNER JOIN HumanResources.Employee AS emp
  36:         ON emp.EmployeeID = hist.EmployeeID
  37:     INNER JOIN Person.Contact AS cont
  38:         ON cont.ContactID = emp.ContactID
  39:     WHERE hist.EndDate IS NULL
  40:  
  41:     -- Drop the temp table.
  42:     DROP TABLE #Departments
  43: END
  44: GO

kick it on DotNetKicks.com

Pagination with ASP.NET and SQL Server 2005

Posted by Jack Altiere on January 2nd, 2008

Anyone that has worked with large data sets has had to figure out this problem at some point.  Any application that returns a large number of records at one time needs to have a way to paginate the results so the end user doesn’t get overwhelmed by the data.  This came up recently on a project I’m working on, and a friend of mine John Miller had an interesting solution to the problem.  I took our solution and converted it to C# for this example.  As I started working on this, I was surprised by how much stuff was actually here.  I have made my sample code available here if you would like to follow along, especially since I will more than likely gloss over some stuff that isn’t critical to the core concept.

Requirements

This example was designed using SQL Server 2005 as a back-end.  That was the environment that we were working with, and there is no requirement to port this to any other database.  As such, we used a stored procedure to do most of the hard work.  You will have to adapt this to your database of choice if you’re on a different platform.

We also used this in a web application, so to make this solution available across pages on the site, a user control made sense here. 

If the code in this article is hard to read I apologize…I had to make some formatting changes to get everything to fit on the page.  It is definitely formatted better in the code download.

Setup

In the download, you will find a .sql script called tPagerDemo.sql.  This script contains the create script for the table that I used for this example, and it handles the creation of the stored procedure that will be doing the work for us, called psPagerUsers.  The example is simple, it’s just a group of names with ID’s associated with them, but if you want to use the same data I did for my example, I included a .txt file called PagerData.txt that contains my sample data set.  This set isn’t large at all, only 32 records.  You can import my sample data directly into your table after it is created using the data import task.

Output

The finished product for us will be a grid showing 5 rows of data at a time, and will contain links to all the pages necessary to paginate our result set.  Although this example does not need this, our pager control will also have a check to make sure only 10 “pages” of links are ever shown at a time.

Here is a screenshot of our finished control:  (yes, I’m a football fan, and my favorite team is the Steelers)

Sample Pager Output

Notice that we’re only showing 5 rows at a time, and that we have a “next” link available.  In this case, there is no “previous” link because we are already on the first page.  Also, we will make the rows per page configurable when we build the control.

Stored Procedure

The key concept in this implementation is that we want the database to do all of the work.  I only want to bring back N records over the wire, where N is the number of rows per page.  The hard part is remembering where we are on the database for each request.  This can be more of a challenge if you are sorting records, or filtering the table results.  We chose to have a stored procedure do the work for us, and use temporary tables do the work of manage the pagination.

The stored procedure has 3 parameters, 2 of them for input, and an output parameter that keeps track of how many total rows there are.  This example didn’t do any sort of filtering, I’m just bringing back all of the records.  If I was filtering the data, I would add another input parameter and use it to filter the records by whatever column I wanted.

   1: SET ANSI_NULLS ON
   2: GO
   3: SET QUOTED_IDENTIFIER ON
   4: GO
   5:  
   6: CREATE PROCEDURE [dbo].[psPagerUsers] 
   7:     @CurrentPage INT = NULL,
   8:     @RowsPerPage INT = NULL,
   9:     @TotalRows INT OUTPUT
  10: AS
  11: BEGIN
  12:     SET NOCOUNT ON;
  13:  
  14:     --Drop temp table if it already exists
  15:     IF OBJECT_ID('tempdb..#tempPagerDemo') IS NOT NULL
  16:         DROP TABLE #tempPagerDemo
  17:  
  18:     --Create temp table
  19:     CREATE TABLE #tempPagerDemo(ID INT IDENTITY(1,1), UserID INT)
  20:  
  21:     --Populate the temp table
  22:     INSERT INTO #tempPagerDemo(UserID)
  23:     SELECT UserID  
  24:     FROM tPagerDemo (NOLOCK) 
  25:     ORDER BY UserID
  26:  
  27:     --Set the TotalRows output parameter
  28:     SET @TotalRows = (SELECT COUNT(*) FROM #tempPagerDemo)
  29:  
  30:     --Get all records if either @CurrentPage or @RowsPerPage 
  31:     --weren't specified in the parameters
  32:     IF (@CurrentPage = 0) OR (@RowsPerPage = 0)
  33:     BEGIN
  34:         SET @CurrentPage = 1
  35:         SET @RowsPerPage = @TotalRows
  36:     END
  37:  
  38:     --Get a page of records from the temp table and 
  39:     --join them with the real table
  40:     SELECT pd.UserID, pd.FirstName, pd.LastName
  41:     FROM #tempPagerDemo (NOLOCK) tmp
  42:     INNER JOIN tPagerDemo (NOLOCK) pd ON tmp.UserID = pd.UserID
  43:     WHERE ID BETWEEN
  44:     ((@CurrentPage - 1) * @RowsPerPage + 1) AND ((@CurrentPage) * @RowsPerPage)
  45:  
  46:     --Drop temp table
  47:     IF OBJECT_ID('tempdb..#tempPagerDemo') IS NOT NULL
  48:         DROP TABLE #tempPagerDemo
  49: END

If you are familiar with stored procedures, this one should make sense to you.  I will cover it at a high level though.  The first thing I do is create a temp table.  This table has an identify field and also a field I can use to link to the main data table.  In this case, since the UserID field is the primary key of the table tPagerDemo, I use a UserID field in my temp table.  

The next thing that happens is the population of the temp table.  This is where you would do any filtering of the data if necessary.  In this case I just select all of the records.  The important thing here is that you are NOT storing all of the data from the main table, only a key that is used to reference the main data table.  After this is done, I set the output parameter to be the number of rows in the temp table so the control knows how many total records are present.

The last step is the most important step.  This is where we take the current page and the rows per page parameters to figure out which records to bring back.  The beauty of this system is that we are using the auto-increment feature of our temp table to do this.  We know that our records are numbered sequentially from 1 to N, so once we are given the current page and rows per page variables, we can calculate exactly which records to bring back.  At this time, we join our subset of the records with the main data table to get all of the necessary information about the record.

Pager Classes

I created 4 classes to handle the pagination logic.  The first is the Pager class.  This class only has a few private members, and a bunch of properties.  The sole purpose of this class is to keep track of where you are in a recordset.  The private variables are totalRows, rowsPerPage, and currentPage.   With these pieces of data, we can derive properties such as TotalPages, PreviousPage, NextPage, FirstPage, LastPage, IsFirstPage, IsLastPage, and HasMultiplePages.  This class also has some public methods for the creation of page number lists, which are pages 1 through 7 in our screenshot above.

The next class is the PaginationPageListItem class.  This class just has a page number and a boolean variable that determines if it’s the current page.   This is because we want the current page to show up as text, and all other pages to show up as links so we can jump to them.

The third class is the PaginationClickEventArgs class.  This class extends the EventArgs class, and just adds a pageNumber field.  This is used by the control to throw an event to the page whenever a link on the control is clicked.  It is important to store the page number to load as part of the arguments because we need to know which rows to load with our stored procedure.

Lastly, we have a PaginatedList<T> class.  This class extends List<T>, and adds a pager to the generic list.  Adding the pager allows us to see which part of the data collection we have in the list.  Remember, the whole key here is that we are only bringing back the records for the specific “page” we are looking at…so our PaginatedList does not contain the entire data set.

To try and keep this article a reasonable length, I’m not going to show you all of these classes here, they are all available in the code download.  For my example, all of these classes were just dropped in the App_Code folder.

Data Access

I decided for this example to use a simple data layer class to grab the data.  I just dropped this in the App_Code section of the website to use it on my page.  Since this example is trivial, I only have 1 method in my class to access the data.  My class looks like this:

   1: public class DLayer
   2: {
   3:    public static PaginatedList<Person> LoadPeople(int currentPage, 
   4:                                                     int rowsPerPage)
   5:    {
   6:       PaginatedList<Person> lstPeople = new PaginatedList<Person>();
   7:  
   8:       using (SqlConnection conn = new SqlConnection("yourConnString"))
   9:       {
  10:          conn.Open();
  11:          using (SqlCommand cmd = new SqlCommand("psPagerUsers", conn))
  12:          {
  13:             SqlDataReader rdr;
  14:             cmd.CommandType = CommandType.StoredProcedure;
  15:             cmd.Parameters.Add("@CurrentPage", SqlDbType.Int).Value = 
  16:                                                             currentPage;
  17:             cmd.Parameters.Add("@RowsPerPage", SqlDbType.Int).Value = 
  18:                                                             rowsPerPage;
  19:  
  20:             // Capture the output parameter.
  21:             SqlParameter paramTotalRows = 
  22:                 new SqlParameter("@TotalRows", SqlDbType.Int);
  23:             paramTotalRows.Direction = ParameterDirection.Output;
  24:             cmd.Parameters.Add(paramTotalRows);
  25:  
  26:             rdr = cmd.ExecuteReader();
  27:             while (rdr.Read())
  28:             {
  29:                Person current = new Person();
  30:                current.UserID = (int)rdr["UserID"];
  31:                current.FirstName = (rdr["FirstName"]).ToString();
  32:                current.LastName = (rdr["LastName"]).ToString();
  33:                lstPeople.Add(current);
  34:             }
  35:             rdr.Close();
  36:  
  37:             // Set up the pager.
  38:             int totalRows = (int)paramTotalRows.Value;
  39:             Pager pgr = new Pager(totalRows, rowsPerPage, currentPage);
  40:             lstPeople.Pager = pgr;
  41:          }
  42:       }
  43:       return lstPeople;
  44:    }
  45: }

All this method does is call the stored procedure we created above to load the relevent people, and then sets up our pager. We use the rowsPerPage and currentPage parameters of this function and just pass them straight to the procedure.  I should point out that this data class references another class I built called Person that just holds the ID, first name, and last name of the person.  This class was also dropped in the App_Code folder. 

Notice that this method returns a PaginatedList of type Person.  We set up the pager for our list after the data has been loaded, and attach it to our list to be used by the control.  Remember that the PaginatedList<T> extends List<T>, so that gives us the ability to bind our result to our GridView to display it to the user.

User Control

OK.  So far we created our stored procedure to access the data, and created a data layer class to load this data and return a PaginatedList<Person>.  We also set up all of the classes needed to create our pager control, so we can finally talk about building the control itself.  I’m not going to go into the specifics for creating a user control here.  I’ll touch on some points, but if you need more specific help, then Google is your friend.

Here is what the layout of our control looks like:

   1: <%@ Control Language="C#" AutoEventWireup="true" CodeFile="PaginationControl.ascx.cs" 
   2: Inherits="PaginationControl" %>
   3: <asp:Panel ID="pnlPager" runat="server">
   4: <table id="tblPager" runat="server" class="PagerTable">
   5:     <tr>
   6:         <td class="LeftCell">
   7:             <asp:LinkButton ID="lbPagerPreviousPage" runat="server" Text="Previous" 
   8:             OnClick="lbPager_Click" CssClass="PagerLink"></asp:LinkButton>
   9:         </td>
  10:         <td class="CenterCell">
  11:             <asp:LinkButton ID="lbPagerPreviousTen" runat="server" Text="<<" 
  12:             OnClick="lbPager_Click" CssClass="PagerLink" />
  13:             <asp:Repeater ID="rptPagerPages" runat="server">
  14:                 <ItemTemplate>
  15:                     <asp:Literal ID="ltPagerDisabledPageLink" runat="server" 
  16:                         Visible='<%# Eval("DisplayAsText") %>' 
  17:                         Text='<%# Eval("PageNumber") %>'></asp:Literal>
  18:                     <asp:LinkButton ID="lbPagerPageLink" runat="server" 
  19:                         Text='<%# Eval("PageNumber") %>' OnClick="lbPager_Click" 
  20:                         CommandArgument='<%# Eval("PageNumber") %>' 
  21:                         CssClass="PagerLink" Visible='<%# Eval("DisplayAsLink") %>'>
  22:                     </asp:LinkButton>
  23:                 </ItemTemplate>
  24:             </asp:Repeater>
  25:             <asp:LinkButton ID="lbPagerNextTen" runat="server" Text=">>" 
  26:             OnClick="lbPager_Click" CssClass="PagerLink" />
  27:             <br /><asp:Literal ID="ltPagerCaption" runat="server"></asp:Literal>
  28:         </td>
  29:         <td class="RightCell">
  30:             <asp:LinkButton ID="lbPagerNextPage" runat="server" Text="Next" 
  31:             OnClick="lbPager_Click" CssClass="PagerLink"></asp:LinkButton>
  32:         </td>
  33:     </tr>
  34: </table>
  35: </asp:Panel>

I am using a Repeater control to display the numbered links, and I also have links to load the next and previous ten pages of links, and to jump to the previous or next page.  A style sheet controls the look and feel of the pager control (as well as the GridView) and has been included in the code download.

The code behind for this control has several items that should be pointed out.  First…..a few of the pager properties are configurable from the web.config.  The first is the amount of links that run across the bottom of the pager control.  In our screenshot above, it is set to 10, so all of our pages show up.  I can change a key I created in the web.config file called PaginationPageNumberLinksRange to make this value 5 (from 10), and while I’m in there I’ll change the PaginationRowsPerPage key to be 6 (from 5).

   1: <appSettings>
   2:     <add key="PaginationPageNumberLinksRange" value="5"/>
   3:     <add key="PaginationRowsPerPage" value="6"/>
   4: </appSettings>

The result of that configuration change would make our example from above look like this:

pagerOutput2

Notice that changing the rows per page to 6 reduced the number of pages we would need from 7 to 6, and since we are now only showing 5 pages at a time we get the >> that will allow us to see the last page.

One of the most important methods in the code behind of the control is the BuildPagination method.

   1: public void BuildPagination(Pager pager)
   2: {
   3:    _pager = pager;
   4:    CurrentPage = pager.CurrentPage;
   5:    string totalText;
   6:  
   7:    if (_pager.TotalPages > 0)
   8:    {
   9:       pnlPager.Visible = true;
  10:       lbPagerPreviousPage.CommandArgument = 
  11:             _pager.PreviousPage.ToString();
  12:       lbPagerNextPage.CommandArgument = 
  13:             _pager.NextPage.ToString();
  14:       lbPagerPreviousPage.Visible = 
  15:             (_pager.HasMultiplePages && (!_pager.IsFirstPage));
  16:       lbPagerNextPage.Visible = 
  17:             (_pager.HasMultiplePages && (!_pager.IsLastPage));
  18:  
  19:       // check that grammar!
  20:       if (_pager.TotalRows == 1)
  21:          totalText = "Record";
  22:       else
  23:          totalText = "Records";
  24:  
  25:       ltPagerCaption.Text =  
  26:         String.Format("Page <strong>{0}</strong> of <strong>{1}</strong><br/>"
  27:                     + "<strong>{2}</strong> {3}", _pager.CurrentPage, 
  28:                     _pager.TotalPages, _pager.TotalRows, totalText);
  29:  
  30:       int startPage = 
  31:         Pager.GetPageNumberListStartPage(_pager, _pageNumberLinksRange);
  32:       int endPage = 
  33:         Pager.GetPageNumberListEndPage(_pager, _pageNumberLinksRange, startPage);
  34:       List<PaginationPageListItem> pageNumbers = 
  35:         Pager.GetPageNumberList(_pager, startPage, endPage);
  36:  
  37:       // Bind the repeater to our list of page items.
  38:       rptPagerPages.DataSource = pageNumbers;
  39:       rptPagerPages.DataBind();
  40:  
  41:       if (startPage > _pager.FirstPage)
  42:       {
  43:          lbPagerPreviousTen.Visible = true;
  44:          lbPagerPreviousTen.CommandArgument = 
  45:             (startPage - 1).ToString();
  46:       }
  47:       else
  48:          lbPagerPreviousTen.Visible = false;
  49:  
  50:       if (_pager.TotalPages > endPage)
  51:       {
  52:          lbPagerNextTen.Visible = true;
  53:          lbPagerNextTen.CommandArgument = 
  54:             (endPage + 1).ToString();
  55:       }
  56:       else
  57:          lbPagerNextTen.Visible = false;
  58:    }
  59:    else
  60:       pnlPager.Visible = false;
  61: }

This is the method that actually creates the list of page numbers by binding the repeater, and it shows (or hides) different elements of the control based on the status of the pager.  A few good examples of this are only showing the previous and next links when they make sense, and only showing the previous / next page groupings when they make sense.

Another important item to cover is the event handling.  I want to generate an event any time a link is clicked so that the page hosting this control can handle this.  I do this with the following code:

   1: public delegate void PaginationClickHandler(object sender, 
   2:     PaginationClickEventArgs args);
   3:  
   4: // I use this syntax just to make sure that there is at least 
   5: // 1 thing subscribed to my event.
   6: public event PaginationClickHandler PaginationClickEvent = delegate { };
   7:  
   8: protected void lbPager_Click(object sender, EventArgs args)
   9: {
  10:   LinkButton lb = (LinkButton)sender;
  11:   PaginationClickEvent(this, 
  12:     new PaginationClickEventArgs(Convert.ToInt32(lb.CommandArgument)));
  13: }

I use the PaginationClickEventArgs class that I created because it is important to be able to keep track of the page we are looking at.  The last thing that is worth pointing out is that the control uses ViewState to keep track of the current page through a property of the control, so it can be accessed from the web form.

   1: public int CurrentPage
   2: {
   3:    get 
   4:    {
   5:       if ((ViewState["CurrentPage"]) == null)
   6:          ViewState["CurrentPage"] = 0;
   7:       return (int)ViewState["CurrentPage"];
   8:    }
   9:    set { ViewState["CurrentPage"] = value; }
  10: }

Page Layout and Binding

For the next step, we need to think about how we are going to display our data to the user.  I chose to use a simple GridView control to show this data.  We will be making a control to handle the pagination aspect, but this control only shows you which data is loaded, and allows you to load other pages of data.  How you display this data is separate from the pagination.  I could have just as easily made this an unordered list using the Repeater control if that’s how I wanted the data to be displayed.

   1: <%@ Register Src="PaginationControl.ascx" TagName="Pagination" TagPrefix="JAL" %>
   2:  
   3: <form id="form1" runat="server">
   4: <div>
   5: <asp:GridView ID="gdvBlogTest" runat="server"
   6:             AutoGenerateColumns="false" 
   7:             EmptyDataText="No Open Projects Found." 
   8:             CssClass="DefaultGrid" 
   9:             HeaderStyle-CssClass="DefaultGridHeader" 
  10:             RowStyle-CssClass="DefaultGridRow" 
  11:             AlternatingRowStyle-CssClass="DefaultGridAltRow">
  12:   <Columns>
  13:      <asp:TemplateField HeaderText="ID">
  14:           <ItemTemplate>
  15:           <asp:Label ID="lblID" runat="server">
  16:           <%# Eval("UserID") %>
  17:           </asp:Label>
  18:           </ItemTemplate>
  19:       </asp:TemplateField>
  20:       <asp:TemplateField HeaderText="First Name">
  21:           <ItemTemplate>
  22:           <asp:Label ID="lblFirstName" runat="server">
  23:           <%# Eval("FirstName") %>
  24:           </asp:Label>
  25:           </ItemTemplate>
  26:       </asp:TemplateField>
  27:       <asp:TemplateField HeaderText="Last Name">
  28:           <ItemTemplate>
  29:           <asp:Label ID="lblLastName" runat="server">
  30:           <%# Eval("LastName") %>
  31:           </asp:Label>
  32:           </ItemTemplate>
  33:       </asp:TemplateField>
  34:   </Columns>
  35: </asp:GridView>
  36: <JAL:Pagination ID="jalPager" runat="server" PagerTableCSSClass="PagerTable" />
  37: </div>
  38: </form>

This shows an abbreviated version of what our .aspx page looks like.  I cut out some of the fluff, such as the Page directive, the link to the style sheet, etc.  I just wanted to show how to register the control that we created, how do declare the control, and where to put it on the page in relation to our GridView. 

The final step is to show how the GridView gets populated, and how we handle the PaginationClick event.  I added these 2 methods to the code behind of web form to handle everything.

   1: protected void Page_Load(object sender, EventArgs e)
   2: {
   3:   PaginatedList<Person> lstPeople;
   4:  
   5:   // Bind the data grid to the list of people.
   6:   lstPeople = DLayer.LoadPeople(CurrentPage, 
   7:     Convert.ToInt32(ConfigurationManager.AppSettings["PaginationRowsPerPage"]));
   8:  
   9:   gdvBlogTest.DataSource = lstPeople;
  10:   gdvBlogTest.DataBind();
  11:  
  12:   // Build the pagination elements.
  13:   jalPager.BuildPagination(lstPeople.Pager);
  14:  
  15:   // Subscribe to the pager event.
  16:   jalPager.PaginationClickEvent += 
  17:     new PaginationControl.PaginationClickHandler(jalPager_PaginationClickEvent);
  18: }
  19: /*------------------------------------------------------------*/
  20: void jalPager_PaginationClickEvent(object sender, PaginationClickEventArgs args)
  21: {
  22:   PaginatedList<Person> lstPeople;
  23:  
  24:   // Determine which page should be displayed and rebind the gridview.
  25:   CurrentPage = args.PageNumber;
  26:   lstPeople = DLayer.LoadPeople(CurrentPage, 
  27:     Convert.ToInt32(ConfigurationManager.AppSettings["PaginationRowsPerPage"]));
  28:  
  29:   gdvBlogTest.DataSource = lstPeople;
  30:   gdvBlogTest.DataBind();
  31:  
  32:   // Rebuild the pagination.
  33:   jalPager.BuildPagination(lstPeople.Pager);
  34: }

The page load takes care of the initial binding of the GridView control, and it also builds the pagination for the PaginationControl using the pager from the PaginatedList<T>.  Remember that this pager object contains information about the specific records that are coming back, that way the GridView and the PaginationControl stay in sync.  It also subscribes to the PaginationClick event.  When one of these events fires, our PaginatedList is repopulated based on the page specified in the PaginationClickEventArgs class, the GridView is rebound, and the PaginationControl has it’s navigation rebuilt.

Once again, you can download the sample code here.  Let me know what you think of this method of handling pagination, I’m interested to hear how you think it could be optimized.  

kick it on DotNetKicks.com

Downloads

Twitter Updates

    Top Commentators

    • No commentators.

    Copyright © 2007 Jack Altiere. All rights reserved.