Real Life SQL and .NET : using SQL with C#: Part XI

The Complete Example with SQL Statements

Now, we will work on it as an example that it has several SQL statements, which used to manipulate data. We will use an ASP example and DataGrid functionality to show these important issues. Especially we will see these SQL issues in the data grouping section. As you remember the ASP.NET DataGrid control renders a multi-column, fully templated grid, and is the most versatile and flexible of all data-bound Web controls in the .NET Framework. The DataGrid's user interface resembles, to some extent, a Microsoft Excel worksheet. Despite its rather advanced programming interface and a full set of attributes, the DataGrid simply generates an HTML table with interspersed hyperlinks to provide interactive functionality, such as sorting and pagination commands.
With the DataGrid control, you can create simple data-bound columns that show data retrieved from a data source, templated columns that let you design the layout of cell contents, and, last but not least, command-based columns that allow you to add specific functionality to a grid.
The DataGrid control is great for data reporting and flexible enough to let you build complex, professional-looking tables of data in which functionalities like pagination and sorting are free. Instead, other functionalities, such as drill-down and master/details, only require a little effort. In this month's column, We will begin one function that the control itself does not provide, but that many people would heartily welcome. Accordingly, let's examine how to automate the production of complex reports in which you have to show summary rows with partial totals.

Our Helpers: DataGrid Items

You bind actual data to an instance of the DataGrid control by setting the control's DataSource property. This property is a generic Object type and supports two configuration scenarios. Normally you set it with a data object that implements the ICollection interface. Typical objects you would use are DataTable and DataView. Alternatively, you set it with another object type, for instance, data readers object. In this case, though, you must turn the custom paging mechanism on; otherwise, an exception would be thrown. In brief, the DataGrid is bound to a paged data source that is a collection object that implements an enumerator or you must provide pagination for yourself.
For a Web control, data binding is enabled, and the user interface is refreshed only when you call the DataBind method. When this happens, the control walks its way through the data source and copies some rows into its Items collection. The Items property represents the contents of the currently displayed page. If the data source supports pagination that is, implements ICollection—the DataGrid selects from DataSource the proper subset of rows that fits into the current page. Otherwise, it assumes that the whole contents of DataSource fits into the current page and loads all of it into Items. Once Items have been filled, the control's user interface is rendered.
What's the lesson here? All that a DataGrid control can safely and consistently display are rows contained in the bound data source. So, if you want to insert summary rows to group some records under a common key and display partial totals, you must figure out a way to insert those summary rows directly into the data source. Having summary rows in the data source is not enough though. In fact, you must be able to distinguish summary and ordinary rows and render the former with different visual styles.
You make sure that the data source contains all the summary rows it needs prior to attaching the data to the control. Next, you hook up the ItemCreated event, detect each summary row, and draw them with a different layout and style. Let's look at how to insert summary rows between the rows of a SQL query. The sample application I'll use to demonstrate my points is based on the Northwind SQL Server database. The application lists all the orders that each customer has issued in a given year. Orders are grouped by year and customer ID. For each customer, an extra row summarizes the total number of orders and their total amount.

Grouping Data with SQL SELECT

Given a year, the following SQL command selects all the orders issued by all customers. Only the sum of all item prices for each order is displayed.
  1. SELECT o.customerid, od.orderid, SUM(od.quantity * od.unitprice) AS price  
  2. FROM Orders o, [Order Details] od  
  3. WHERE Year(o.orderdate) = @TheYear AND od.orderid = o.orderid  
  4. GROUP BY o.customerid, od.orderid  
  5. ORDER BY o.customerid 
The GROUP BY clause of the SELECT statement in T-SQL language provides the WITH ROLLUP clause that adds predefined summary rows to the result set. Of course, such a summary row has the layout of all other columns, but the content of each column can be customized to some extent. The following statements illustrate how to modify the previous command to allow for summary rows.
  1. DECLARE @TheYear int  
  2. SET @TheYear = 1998  
  4. SELECT CASE GROUPING(o.customerid) WHEN 0
  6. THEN o.customerid ELSE '(Total)'  
  7. END AS MyCustomerID,  
  8. CASE GROUPING(od.orderid) WHEN 0  
  9. THEN od.orderid ELSE - 1 END AS MyOrderID,  
  10.     SUM(od.quantity * od.unitprice) AS price  
  11. FROM Orders o, [Order Details] od  
  12. WHERE Year(orderdate) = @TheYear AND od.orderid = o.orderid  
  13. GROUP BY o.customerid, od.orderid WITH ROLLUP  
  14. ORDER BY o.customerid, price 
GROUPING is the T-SQL aggregate function that works in conjunction with ROLLUP in the body of a GROUP BY clause. The use of the GROUPING operator causes a new column to be added to the result set. This column contains a value of 1 if the row has been added by the ROLLUP operator, and therefore, is a summary row. Otherwise, the column takes a value of 0. By using a CASE, WHEN, END statement you can merge this new column with the grouping column.
In the example above, the MyCustomerID column contains the value of the CustomerID column and the string '(Total)' in all rows created by grouping on that column. Likewise, the MyOrderID column contains the order ID and –1 when the row represents a subtotal.
To summarize data, SQL Server provides a few more options, such as the WITH CUBE operator and the COMPUTE BY clause. As you can imagine, all these options are not completely equivalent, although the functionality of one somehow intersects the working of the other. In particular, WITH CUBE generates a summary row for every possible combination of groups and subgroups in the result set. WITH ROLLUP, instead, groups hierarchically according to the order in which the grouping columns are specified. Finally, COMPUTE BY, which SQL Server 2000 supports only for backward compatibility, works in much the same way WITH ROLLUP does, but returns multiple result sets and is not treated as efficiently as ROLLUP by the query optimizer.

Presenting Grouped Data using DataGrid

The data source you obtain using the WITH ROLLUP operator already contains all the information you may need to produce an effective report. As you may have noticed, the statement adds a top row that contains the total of all the orders issued by all customers. When you use the WITH ROLLUP operator, the number and the structure of the rows generated may change significantly if you modify the order of the grouping rows. The extra row is a consequence of the particular syntax I have chosen to use. If you don't need that piece of information, just drop it off the result set prior to bind. Alternatively, you could move that row at the bottom of the data set.
The code shown below demonstrates how to execute the rollup statement. The year to consider is a parameter read out of textbox. The result set is temporarily stored in a DataSet object. In this sample application, I'll cache the DataSet object in a Session slot. This is not a choice that should be taken lightly in a real-world context. In general, any byte you store in Session should have a good reason to be there.
  1. <asp:DataGrid id="grid" runat="server" AutoGenerateColumns="false" AllowPaging="true" PageSize="15" Font-Size="xx-small" CellSpacing="0" CellPadding="4" GridLines="both" BorderStyle="solid" BorderColor="skyblue" BorderWidth="1" OnItemCreated="ItemCreated" OnPageIndexChanged="PageIndexChanged">  
  2.      <headerstyle backcolor="skyblue" font-size="9pt" font-bold="true" />  
  3.      <itemstyle backcolor="#eeeeee" />  
  4.      <pagerstyle backcolor="skyblue" font-name="webdings" font-size="10pt" PrevPageText="3" NextPageText="4" />  
  5.      <Columns>  
  6.           <asp:BoundColumn DataField="MyCustId" HeaderText="Customer" />  
  7.           <asp:BoundColumn DataField="MyOrderId" HeaderText="Order #" />  
  8.           <asp:BoundColumn DataField="price" HeaderText="Amount" DataFormatString="{0:c}">  
  9.                <itemstyle horizontalalign="right" />  
  10.           </asp:BoundColumn>  
  11.      </Columns>  
  12. </asp:DataGrid>  
  13. private DataSet PhysicalDataRead()  
  14. {  
  15. String strCnn = "SERVER=localhost;DATABASE=northwind;UID=sa;";  
  16. SqlConnection conn = new SqlConnection(strCnn);  
  17. // Command text using WITH ROLLUP  
  18. StringBuilder sb = new StringBuilder("");  
  19. sb.Append("SELECT ");  
  20. sb.Append(" CASE GROUPING(o.customerid) WHEN 0 ");  
  21. sb.Append(" THEN o.customerid ELSE '(Total)' END AS MyCustID, ");  
  22. sb.Append(" CASE GROUPING(od.orderid) WHEN 0 ");  
  23. sb.Append(" THEN od.orderid ELSE -1 END AS MyOrderID, ");  
  24. sb.Append(" SUM(od.quantity*od.unitprice) AS price ");  
  25. sb.Append("FROM Orders o, [Order Details] od ");  
  26. sb.Append("WHERE Year(orderdate)=@nYear AND od.orderid=o.orderid ");  
  27. sb.Append("GROUP BY o.customerid, od.orderid WITH ROLLUP ");  
  28. sb.Append("ORDER BY o.customerid, price");  
  29. String strCmd = sb.ToString();  
  30. sb = null;  
  31. SqlCommand cmd = new SqlCommand();  
  32. cmd.CommandText = strCmd;  
  33. cmd.Connection = conn;  
  34. SqlDataAdapter da = new SqlDataAdapter(strCmd, strConn);  
  35. da.SelectCommand = cmd;  
  36. // Set the "year" parameter  
  37. SqlParameter p1 = new SqlParameter("@nYear", SqlDbType.Int);  
  38. p1.Direction = ParameterDirection.Input;  
  39. p1.Value = Convert.ToInt32(txtYear.Text);  
  40. cmd.Parameters.Add(p1);  
  41. DataSet ds = new DataSet();  
  42. da.Fill(ds, "Orders");  
  43. return ds;  
To make summary rows clearly stand out from the grid's page, you need to change the style and the layout of the rows. This can be accomplished from within the ItemCreated event handler. The idea is to detect the summary row by checking the order ID and then modify the cell layout and the style. In the result set, a summary row is characterized by an order ID of –1. The value of –1 is arbitrary and stems from the statement used.
  1. CASE GROUPING(od.orderid) WHEN 0 THEN od.orderid ELSE -1 END AS MyOrderID 
If you don't use the GROUPING operator on the orderid column, the column value would be NULL for all summary rows.

How We can Modify Layout and Style?

A DataGrid allows you to modify both the style and layout of the constituent cells. You do this by hooking the ItemCreated event. The event fires each time the control processes a child item—header, footer, rows, and pager. The event handler receives a parameter of type DataGridItemEventArgs from which you extract the type of the item being processed. A summary row is a DataGrid row, and as such, it can be of type Item or AlternatingItem. So, when writing the ItemCreated handler, make sure you process the cell only if the item is of the right type. The following listing outlines the code you need.
  1. public void ItemCreated(Object sender, DataGridItemEventArgs e) {  
  2.      // Get the type of the newly created item  
  4.      ListItemType itemType = e.Item.ItemType;  
  6.      if (itemType == ListItemType.Item ||  
  7.           itemType == ListItemType.AlternatingItem) {  
  8.           // Get the data bound to the current row  
  9.           DataRowView drv = (DataRowView) e.Item.DataItem;  
  10.           if (drv != null) {  
  11.                // Check here the app-specific way to detect whether the  
  12.                // current row is a summary row  
  13.           }  
  14.      }  
If the item being created is a DataGrid item (or an alternating item), you access the data bound to the row through the DataItem property. Depending on the type of the object the DataGrid is bound to, the DataItem property points to a different row object. You get a DataRowView object if the grid is bound to a DataView and a DataRow object if the source is expressed in terms of a DataTable object. In the sample application, I used a DataView object to populate the grid. Subsequently, the data object for a single row is a DataRowView object. Once you hold the data row object, you apply some application-specific rules to determine whether that row is or is not a summary row. In the sample application, a summary row has the MyOrderID field set to –1.
  1. if ((int) drv["MyOrderID"] == -1) {  
  2.      // Modify style and layout here.  
  3.      // --> Set the background color to white and use bold font  
  4.      e.Item.BackColor = Color.White;  
  5.      e.Item.Font.Bold = true;  
Actually, a DataGrid row is merely a table row. As such, it lends itself well to cell removal and other adjustments. Let's see how to render a summary row using a single cell that spans over all existing columns.
  1. if ((int) drv["MyOrderID"] == -1) {  
  2.      // Modify style and layout here.  
  3.      e.Item.BackColor = Color.White;  
  4.      e.Item.Font.Bold = true;  
  5.      e.Item.Cells.RemoveAt(0); // remove CustID  
  6.      e.Item.Cells.RemoveAt(0); // remove Order #, now the first  
  7.      // Span and right-align the cell left  
  8.      e.Item.Cells[0].ColumnSpan = 3;  
  9.      e.Item.Cells[0].HorizontalAlign = HorizontalAlign.Right;  
Of the original three cells, the first two are removed and the latter—now with an index of 0—is right-aligned and spanned over the width of the outer table. If you want to display some custom text on the summary row, then be ready to face an additional problem.
Suppose that you want to add some text to comment on the subtotal and, at the same time, have the subtotal appear in the same column as the amount of the single order. In this case, you only need to remove one cell.
  1. e.Item.Cells.RemoveAt(1); // remove the order # cell  
  2. e.Item.Cells[0].ColumnSpan = 2; // span the custID cell  
  3. e.Item.Cells[1].HorizontalAlign = HorizontalAlign.Right;  
  4. e.Item.Cells[0].Text = "Total is"
The key fact to consider here is that both Item and AlternatingItem rows are bound rows. Their definitive text is set only during the OnItemDataBound event. As you may have guessed already, the OnItemDataBound event fires after that the item has been created. As a result, any text you assign to cells while processing ItemCreated has silently overwritten one event later. You hook up the OnItemDataBound event by setting the DataGrid's OnItemDataBound property.
  1. <asp:DataGrid id="grid" runat="server" AutoGenerateColumns="false" : OnItemCreated="ItemCreated" OnItemDataBound="ItemDataBound" OnPageIndexChanged="PageIndexChanged"
The structure of the code for ItemDataBound is shown below.
  1. public void ItemDataBound(Object sender, DataGridItemEventArgs e) {  
  2.      DataRowView drv = (DataRowView) e.Item.DataItem;  
  3.      if (drv == null)  
  4.           return;  
  5.      if ((int) drv["MyOrderID"] == -1) {  
  6.           if (drv["MyCustomerID"].ToString() == "(Total)") {  
  7.                e.Item.BackColor = Color.Yellow;  
  8.                e.Item.Cells[0].Text = "Orders total";  
  9.           } else  
  10.                e.Item.Cells[0].Text = "Customer subtotal";  
  11.      }  
Here is full source listing for this application:
  1. <%@ Page Language="C#" Inherits="BWSLib.MyPage" src="Summary.cs" Trace="false" %>  
  2. <html>  
  3.      <title>Summary Rows</title>  
  4.      <style>  
  5.      a {  
  6.           behavior: url(..\..\;  
  7.      }  
  9.      hr {  
  10.           height: 2px;  
  11.           color: black;  
  12.      }  
  14.      .StdTextBox {  
  15.           font-family: verdana;  
  16.           font-size: x-small;  
  17.           border: solid 1px lack;  
  18.           filter: progid:DXImageTransform.Microsoft.dropshadow(OffX=2OffY=2,  
  19.                olor='gray'Positive='true');  
  20.      }  
  22.      .StdText {  
  23.           font-family: verdana;  
  24.           font-size: x-small;  
  25.      }  
  26.      </style>  
  28.      <BODY bgcolor="ivory" style="font-family:verdana;font-size:small">  
  29.           <h2>Orders and Customers</h2>  
  30.           <!-- ASP.NET Form -->  
  31.           <form runat="server" ID="Form2">  
  32.                <!-- Grid and the remainder of the page -->  
  33.                <table>  
  34.                     <tr>  
  35.                          <td valign="top">  
  36.                               <asp:DataGrid id="Datagrid1" runat="server" AutoGenerateColumns="false" AllowPaging="true" PageSize="15" Font-Size="xx-small" CellSpacing="0" CellPadding="4" DataKeyField="MyCustomerId" BorderStyle="solid" BorderColor="skyblue" BorderWidth="1" GridLines="both" OnItemCreated="ItemCreated" OnItemDataBound="ItemDataBound" OnPageIndexChanged="PageIndexChanged">  
  37.                                    <headerstyle backcolor="skyblue" font-size="9pt" font-bold="true" />  
  38.                                    <itemstyle backcolor="#eeeeee" />  
  39.                                    <pagerstyle backcolor="skyblue" font-name="webdings" font-size="10pt" revPageText="3" NextPageText="4" />  
  40.                                    <Columns>  
  41.                                         <asp:BoundColumn DataField="MyCustomerId" HeaderText="Customer" />  
  42.                                         <asp:BoundColumn DataField="MyOrderId" HeaderText="Order #" />  
  43.                                         <asp:BoundColumn DataField="price" HeaderText="Amount" DataFormatString="{0:c}">  
  44.                                              <itemstyle horizontalalign="right" />  
  45.                                         </asp:BoundColumn>  
  46.                                    </Columns>  
  47.                               </asp:DataGrid>  
  48.                          </td>  
  49.                          <td valign="top" width="20px"></td>  
  50.                          <td valign="top">  
  51.                               <b>Year</b>  
  52.                               <asp:dropdownlist runat="server" id="ddYears">  
  53.                                    <asp:listitem runat="server" ID="Listitem1" NAME="Listitem1">1998</asp:listitem>  
  54.                                    <asp:listitem runat="server" ID="Listitem2" NAME="Listitem2">1997</asp:listitem>  
  55.                                    <asp:listitem runat="server" ID="Listitem3" NAME="Listitem3">1996</asp:listitem>  
  56.                               </asp:dropdownlist>  
  57.                               <asp:linkbutton runat="server" text="Load..." onclick="OnLoadYear" ID="Linkbutton1" AME="Linkbutton1" />  
  58.                               <br><br>  
  59.                               <asp:label runat="server" cssclass="StdText" id="lblMsg" />  
  60.                          </td>  
  61.                     </tr>  
  62.                </table>  
  63.                <hr>  
  64.           </form>  
  65.      </body>  
  67. </html> 
Here is full source listing for this application:
  1. <%@ Page Language="C#"Inherits="BWSLib.MyPage"src="Summary.cs"Trace="false"%>a {  
  2.      behavior: url(..\..\;  
  3. }  
  5. hr {  
  6.      height2px;  
  7.      colorblack;  
  8. }  
  10. .StdTextBox {  
  11.      font-familyverdana;  
  12.      font-sizex-small;  
  13.      bordersolid 1px black;  
  14.      filter: progid:DXImageTransform.Microsoft.dropshadow(OffX=2, OffY=2, Color='gray', Positive='true');  
  15. }  
  17. .StdText {  
  18.      font-familyverdana;  
  19.      font-sizex-small;  

And here is C# code-behind file:
  1. // Summary.cs - code-behind file  
  2. namespace BWSLib {  
  3.      using System;  
  4.      using System.Web.UI;  
  5.      using System.Web.UI.WebControls;  
  6.      using System.Data;  
  7.      using System.Data.SqlClient;  
  8.      using System.Drawing;  
  9.      using System.Text;  
  10.      public class MyPage: Page {  
  11.           // Declare as PUBLIC or PROTECTED members all  
  12.           // the controls in the layout  
  13.           protected DataGrid grid;  
  14.           protected Label lblMsg;  
  15.           protected DropDownList ddYears;  
  16.           // Page OnLoad  
  17.           protected override void OnLoad(EventArgs e) {  
  18.                if (!IsPostBack) {  
  19.                     // Load data and refresh the view  
  20.                     DataFromSourceToMemory("MyDataSet");  
  21.                     UpdateDataView();  
  22.                }  
  23.           }  
  24.           // DataFromSourceToMemory  
  25.           private void DataFromSourceToMemory(String strDataSessionName) {  
  26.                // Gets rows from the data source  
  27.                DataSet oDS = PhysicalDataRead();  
  28.                // Stores it in the session cache  
  29.                Session[strDataSessionName] = oDS;  
  30.           }  
  31.           // PhysicalDataRead  
  32.           private DataSet PhysicalDataRead() {  
  33.                String strCnn = "server=localhost;initial catalog=northwind;uid=sa;";  
  34.                SqlConnection conn = new SqlConnection(strCnn);  
  35.                // Command text using WITH ROLLUP  
  36.                StringBuilder sb = new StringBuilder("");  
  37.                sb.Append("SELECT ");  
  38.                sb.Append(" CASE GROUPING(o.customerid) WHEN 0 THEN o.customerid ELSE '(Total)' ND AS MyCustomerID, ");  
  39.                sb.Append(" CASE GROUPING(od.orderid) WHEN 0 THEN od.orderid ELSE -1 END AS yOrderID, ");  
  40.                sb.Append(" SUM(od.quantity*od.unitprice) AS price ");  
  41.                sb.Append("FROM Orders o, [Order Details] od ");  
  42.                sb.Append("WHERE Year(orderdate) = @TheYear AND od.orderid=o.orderid ");  
  43.                sb.Append("GROUP BY o.customerid, od.orderid WITH ROLLUP ");  
  44.                sb.Append("ORDER BY o.customerid, price");  
  45.                String strCmd = sb.ToString();  
  46.                sb = null;  
  47.                SqlCommand cmd = new SqlCommand();  
  48.                cmd.CommandText = strCmd;  
  49.                cmd.Connection = conn;  
  50.                SqlDataAdapter da = new SqlDataAdapter();  
  51.                da.SelectCommand = cmd;  
  52.                // Set the "year" parameter  
  53.                SqlParameter p1 = new SqlParameter("@TheYear", SqlDbType.Int);  
  54.                p1.Direction = ParameterDirection.Input;  
  55.                p1.Value = Convert.ToInt32(ddYears.SelectedItem.Text);  
  56.                cmd.Parameters.Add(p1);  
  57.                // The DataSet contains two tables: Orders and Orders1.  
  58.                // The latter is renamed to "OrdersSummary" and the two will be put into  
  59.                // relation on the CustomerID field.  
  60.                DataSet ds = new DataSet();  
  61.                da.Fill(ds, "Orders");  
  62.                return ds;  
  63.           }  
  64.           // Refresh the UI  
  65.           private void UpdateDataView() {  
  66.                // Retrieves the data  
  67.                DataSet ds = (DataSet) Session["MyDataSet"];  
  68.                DataView dv = ds.Tables["Orders"].DefaultView;  
  69.                // Re-bind data  
  70.                grid.DataSource = dv;  
  71.                grid.DataBind();  
  72.           }  
  73.           // EVENT HANDLER: ItemCreated  
  74.           public void ItemCreated(Object sender, DataGridItemEventArgs e) {  
  75.                // Get the newly created item  
  76.                ListItemType itemType = e.Item.ItemType;  
  77.                ///////////////////////////////////////////////////////////////////  
  78.                // ITEM and ALTERNATINGITEM  
  79.                if (itemType == ListItemType.Item || itemType == ListItemType.AlternatingItem) {  
  80.                     DataRowView drv = (DataRowView) e.Item.DataItem;  
  81.                     if (drv != null) {  
  82.                          // Check here the app-specific way to detect whether the  
  83.                          // current row is a summary row  
  84.                          if ((int) drv["MyOrderID"] == -1) {  
  85.                               // Modify the row layout as needed. In this case,  
  86.                               // + change the background color to white  
  87.                               // + Group the first two cells and display company name and #orders  
  88.                               // + Display the total of orders  
  89.                               // Graphical manipulations can be done here. Manipulations that require  
  90.                               // data access should be done hooking ItemDataBound. They can be done  
  91.                               // in ItemCreated only for templated columns.  
  92.                               e.Item.BackColor = Color.White;  
  93.                               e.Item.Font.Bold = true;  
  94.                               e.Item.Cells.RemoveAt(1); // remove the order # cell  
  95.                               e.Item.Cells[0].ColumnSpan = 2; // span the custID cell  
  96.                               e.Item.Cells[1].HorizontalAlign = HorizontalAlign.Right;  
  97.                          }  
  98.                     }  
  99.                }  
  100.           }  
  101.           // EVENT HANDLER: PageIndexChanged  
  102.           public void PageIndexChanged(Object sender, DataGridPageChangedEventArgs e) {  
  103.                grid.CurrentPageIndex = e.NewPageIndex;  
  104.                UpdateDataView();  
  105.           }  
  106.           // EVENT HANDLER: ItemDataBound  
  107.           public void ItemDataBound(Object sender, DataGridItemEventArgs e) {  
  108.                // Retrieve the data linked through the relation  
  109.                // Given the structure of the data ONLY ONE row is retrieved  
  110.                DataRowView drv = (DataRowView) e.Item.DataItem;  
  111.                if (drv == null)  
  112.                     return;  
  113.                // Check here the app-specific way to detect whether the  
  114.                // current row is a summary row  
  115.                if ((int) drv["MyOrderID"] == -1) {  
  116.                     if (drv["MyCustomerID"].ToString() == "(Total)") {  
  117.                          e.Item.BackColor = Color.Yellow;  
  118.                          e.Item.Cells[0].Text = "Orders total";  
  119.                     } else  
  120.                          e.Item.Cells[0].Text = "Customer subtotal";  
  121.                }  
  122.           }  
  123.           public void OnLoadYear(Object sender, EventArgs e) {  
  124.                DataFromSourceToMemory("MyDataSet");  
  125.                UpdateDataView();  
  126.           }  
  127.      }  

Similar Articles