RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


Nested DataGrids Simplify Hierarchical Data Display : Page 2

Learn how to use nested DataGrids to display hierarchical data and avoid the maintenance nightmare of dynamically-created HTML tables.

To Normalize or Not to Normalize
After you decide to retrieve all data prior to displaying it you face another question: how do you store it all? The problem actually breaks down into two questions. First, what physical data structure do you use? Second, how do you hold hierarchical data? XML, custom classes, datasets, and strongly typed datasets are all options for the first question. There are pros and cons, but for the most part it comes down to personal preference. This article uses strongly typed datasets.

Figure 2 summarizes the second question.

Figure 2. Normalized vs. Denormalized Data Structures: You can hold hierarchical data in either normalized form as related tables, or in denormalized form, with all the data in a single table.
The first illustration in Figure 2 shows the normalized approach for the Orders search page and contains four tables. It is based upon the needs of the UI layer, for instance note that customer name was actually denormalized into the orders table. The second illustration shows the denormalized approach and contains a single table with duplicate order and product information.

The denormalized approach requires looping through all rows and trying to spot when data from an outer loop changes. In pseudo code:

   retrieve denormalized data
   sort data so you can tell when outer data changed
   for each row {
      if this a new product i.e. OldProductId != NewProductId    
           write out a table header with product information
      write a nested row from inner data (ProductMonthCount)
Not pretty. This approach presents a big disadvantage in that it still doesn't allow the wonderful benefits of non-dynamically created Web controls—namely, the separation of display from logic.

What's So Difficult About Nesting?
Non-nested web controls are easy. You have two options. Either, you set your control's DataSource property and call the DataBind() method in the code (this is the most common approach), or you expose your data source as a protected property in the code and use the data-binding syntax in the page:

   protected DataTable Orders = GetDataSet().Tables[0];
   <asp:DataGrid DataSource='<%# Orders %>'>
While the data-binding syntax approach works fine with nested controls, the DataBind() approach doesn't. The problem is that is that the code behind page cannot easily access a control that is nested inside another, especially for multi-record controls.

Data Binding Syntax and Control Hierarchy
The data-binding syntax can be a little obscure, but it works well for 90 percent of hierarchical problems, so a thorough understanding is important. Suppose you list Orders in a parent Repeater control, then all the Products for that order inside of a nested Repeater control. The DataSource property for the inner repeater might look like:

   "Orders_Products") %>'
Figure 3. Hierarchy of Multi-Record Controls: The figure shows how the Container property of nested multi-row controls refers to an item in its parent multi-row control.

   "Orders_Products") %>'
Both code snippets bind to all "child rows" (the Products) for the current Order, which they retrieve from their "Container" controls. But what is a Container? And which snippet should you use?

When the .Net framework binds multi-record controls such as a DataRepeater to data, it creates child controls for each record. For DataGrids each child is a DataGridItem, for DataLists a DataListItem, and for Repeaters a RepeaterItem. Within nested controls the Container always refers to the next closest ControlItem (for lack of a better word). Thus, if you have two nested Repeaters and the inner Repeater uses Container, it references a RepeaterItem of the outer Repeater. You can think of ControlItems as the HTML rows that are created by multi-record controls and Container as referencing one of those rows. The diagram in Figure 3 shows a DataGrid with a nested Repeater. The DataGrid has been bound to a table with two orders (#1 and #2). The nested Repeater uses the data-binding syntax and the Container property to reference the current DataGridItem and ultimately retrieve products in the order.

When the logic reaches the ControlItem, you can reference its DataItem property, which represents the corresponding data record. When a control's DataSource is a DataSet or a Typed DataSet, DataItem is either of type DataRow or DataRowView, (say that 10 times fast) and so you must cast it appropriately.

Figure 4. Data Relations: The relationship between tables lets you call GetChildRows() to retrieve child rows of a row in the parent table.
Unfortunately, it is hard to predict whether you should cast a DataItem to a DataRow or a DataRowView (as in the two code snippets shown earlier). As a guideline, DataItems tend to be DataRowViews in controls whose parent was bound in the code behind and DataRows in controls whose parent used the data-binding syntax. If you cast to a DataRowView, you need to take the additional step of retrieving the View's Row. Once you have the DataRow you can get all related child rows (Orders in the example) with a call to GetChildRows(), which takes a relation name as a parameter (see Figure 4).

When you thoroughly understand this data-binding syntax, you can solve ninety percent of your hierarchical data problems. If you don't, I highly recommend re-reading this section; understanding it will greatly enhance your ability to write useful solutions.

Incidentally, when using data-binding syntax remember to add this Imports statement at the top of your page.

   <%@ Import Namespace="System.Data" %>
If you omit the preceding Imports statement, you'll get the exception: "The type or namespace name 'DataRowView' could not be found."

Close Icon
Thanks for your registration, follow us on our social networks to keep up-to-date