Paging through Resultsets in an ASP.NET application (Part 2)

By: CodeGear

Abstract: This article shows how to display pageable data in an ASP.NET application developed with C#Builder using BDP.NET managed provider for Interbase. Covers Custom Paging mechanism.

Paging through ResultSets in an ASP

Paging through ResultSets in an ASP.NET application
Part 2: Implementing a custom-paging solution

By Sam Kapoor
Borland Software Corporation

In the previous article we talked about using the default-paging mechanism of the DataGrid component. We also discussed about the drawback of using this approach in certain real-world situations. In this article we shall discuss the second approach to paging (using a customized-paging solution) and will show how to implement this using C#Builder.

Custom-Paging: The DataGrid component includes another property named AllowCustomPaging that when set to True disables the built-in-paging mechanism of the DataGrid. Instead it expects the DataSource of the DataGrid to contain only the records that need to be displayed on a particular page. The big advantage of using this approach is that only the number of rows that need to be displayed on a given page (for example 10) need to be stored in the web-servers memory as opposed to the entire resultset. This in-turn can result in a much more scalable solution (especially for situations in which each session needs to have its own copy of the data. For even better scalability though one should have a middle-tier that can be scaled out).

The difficulty in implementing a custom-paging solution lies in the fact that different databases employ different techniques for returning a limited resultset.

Implementing a custom-paging solution: In this article we shall re-write the previous example (which used the Employee table from the Employee.gdb Interbase Database) to use custom-paging. Let us start by creating a new ASP.NET Web application.

Lets then select a DataGrid from the Web Controls list and drop it on our Web-form design surface. Set the DataGrids AutoFormat property to Professional1. Then set the AllowCustomPaging and the AllowPaging property to True and let the PageSize stay at 10. By setting the AllowPaging property to True we indicate to the DataGrid that a pager is to used and by setting the AllowCustomPaging property to True we indicate that the DataGrid should not implement any algorithm to page over the bound DataSource.

VirtualItemCount: When using the default-paging mechanism the DataGrid infers the total number of pages it needs to display by looking at the record count of the DataSource. This does not work in the Custom-paging scenario because the DataSource does not have all the records loaded as it only has the subset of records that are to be displayed on the current page. Hence we must provide a way for the DataGrid component to know how many pages to display (if using a numbered pager) or when to enable or disable the Next/Prev link (if using the Next/Prev pager). We do that by setting the VirtualItemCount property of the DataGrid to the total number of rows in the Employee table.

To get to work lets drop a BdpConnection and a BdpCommand component on our Design surface. We can do this by double-clicking on the 2 components. Set the BdpConnections ConnectionString property to the Interbase connection and the BdpCommands CommandText property to the appropriate SQL to get the record count from the Employee table. The BdpCommand has a CommandTextEditor that can be invoked to do this.

           

The BdpCommands Connection property is automatically set to the BdpConnection.

Lets now define a method that we can use to set the DataGrids VirtualItemCount property.

     public int setVirtualCount() {

bdpCommand1.Connection.Open();

int nCount = (int) bdpCommand1.ExecuteScalar();

bdpCommand1.Close();

bdpCommand1.Connection.Close();

return nCount;

}

   

The ExecuteScalar method returns the value in the 0,0 postion (or the first column in the row in the resultset). When the page is loaded for the first time we must set the DataGrid components VirtualItemCount property to the return value from our setVirtualCount method. The only 2 requirements for Custom-paging to work are setting the VirtualItemCount property of the DataGrid and setting its AllowCustomPaging property.

Devising a paging strategy: Since we are going to implement the logic for allowing our resultset to be paged we must figure out a way to be able to scroll back and forth in our query. Since our DataGrid is only going to show 10 rows on a page the trick we will employ is to save the first and last Employee_Number (our key in the Employee Table) that is displayed on the grid and save it to the pages ViewState. The ViewState is the page call context and is implemented as a hidden field (encoded as a Base64 string) that gets written to the page when sent back to the browser. Our logic is going to be such that if the next button is clicked we will execute a query that returns the first 10 rows that have an Employee Number greater than what is stored in the ViewStates last employee number. Conversely when the user clicks the previous link on the pager we shall execute a query that returns the last 10 rows that are less than the first employee number stored in the ViewState. Once we get this logic implemented we shall be able to have customized paging. Each request to the page will execute a query that will only return 10 rows at the maximum. We must point out here that this strategy will only work with queries that have a unique key (which for most tables is not a problem).

Interbase specific SQL: One of the challenges in employing a custom-paging strategy is the dependence on database specific SQL to accomplish the task. With Interbase we can use the rows clause of the Select statement that will restrict our resultset to 10 rows. MS SQL Server has a TOP clause and Oracle has a RowNum clause. The included zip file contains the source of the GET_EMP stored procedure.

The stored procedure takes 3 parameters. NumRows indicates the number of rows to return, EmpNo is the employee number to be passed in (our key), and Prev is a boolean flag which is True if the Previous link is clicked else False. 

If the Next link is clicked we must execute a SQL such as :

      Select * from GET_EMP(10, 40, False);

Here the 10 is the number of rows to retrieve, 40 is the last Employee number displayed on the page and False implies that we are wanting to move forward.

If the Prev link is clicked our SQL query will be:

      Select * from GET_EMP(10, 30, True) Order by EMP_NO;

Here the 10 is the number of rows, 30 is the first employee number displayed on the page and True implies that we are wanting to scroll back. The Order By clause is required to get the resultset in the correct order.

Initialization steps: When the page is requested for the first time we need to perform some initialization of the ViewState of the page.

     private void Page_Load(object sender, System.EventArgs e) {

if (!Page.IsPostBack) { //first time page requested

 dataGrid1.VirtualItemCount = setVirtualCount();

ViewState["LastEmpNo"] = 0;

ViewState["FirstEmpNo"] = 0;

Next = true;

bindData();

  }

}

Here we initialize the entries in the ViewState to 0 so that the first page is retrieved with a LastEmpNo of 0 (which would return the first 10 rows).

Trapping the PageIndexChanged Event of the DataGrid: In order to determine in which direction we need to execute the query (forwards or backwards) we need to determine the pagers link that was clicked by the user. When the user clicks the pagers link (next/prev) the DataGrid fires the ItemCommand event. The CommandArgument member is set to either Next or Prev.

private void dataGrid1_ItemCommand(object source, System.Web.UI.WebControls.DataGridCommandEventArgs e) {

if (e.CommandArgument.ToString() == DataGrid.NextPageCommandArgument){

  Next = true;

}

else

  Next = false;

}

After the ItemCommand event fires the DataGrid fires its PageIndexChanged event in which we can execute the query and re-bind the DataGrid.

private void dataGrid1_PageIndexChanged(object source, System.Web.UI.WebControls.DataGridPageChangedEventArgs e) {

dataGrid1.CurrentPageIndex = e.NewPageIndex;

bindData();

}

 

 

void bindData() {

reader = CreateDS();

dataGrid1.DataSource = reader;

dataGrid1.DataKeyField = "EMP_NO";

dataGrid1.DataBind();

ViewState["FirstEmpNo"] = dataGrid1.DataKeys[0];

ViewState["LastEmpNo"] = dataGrid1.DataKeys[dataGrid1.Items.Count - 1];

reader.Close();

this.bdpCommand1.Close();

this.bdpConnection1.Close();

}

 

Notice here after binding the DataSource to the DataGrid we set the ViewState to hold the First Employee Number displayed in the DataGrid and the Last Employee Number to use for the next query (to move forwards or backwards).

private BdpDataReader CreateDS() {

bdpCommand1.Connection.Open();

if (Next) {

  bdpCommand1.CommandText = "Select * from Get_Emp(" +

  dataGrid1.PageSize.ToString() + "," + ViewState["LastEmpNo"] +

 ",FALSE)";

}

else {

  bdpCommand1.CommandText = "Select * from Get_Emp(" +

 dataGrid1.PageSize.ToString() + "," + ViewState["FirstEmpNo"] +

",TRUE) ORDER BY EMP_NO";

}

return bdpCommand1.ExecuteReader();

}

We use the same BdpCommand component to issue our query. However, we use an instance of a BdpDataReader to bind as a DataSource to the DataGrid instead of a DataSet as it is much faster.

 

The complete code-behind C# class can be found here

Conclusions: As you can see with a little bit of work we can devise an effective customized paging strategy. The trick is coming up with the correct SQL (database specific usually) to get the correct subset of records. This technique will also work with tables that get updated/deleted. For web-applications that serve a large number of concurrent users who need access to different subsets of data a customized paging solution is far better than the default-paging solution. To achieve greater scalability one can encapsulate the logic for the customized paging in a middle-tier.

 


Server Response from: ETNASC04