This is a migrated thread and some comments may be shown as answers.

RadGrid: Problem with binding to a hierarchical data structure

2 Answers 270 Views
Grid
This is a migrated thread and some comments may be shown as answers.
Heikki Heikkinen
Top achievements
Rank 1
Heikki Heikkinen asked on 20 Oct 2010, 01:10 PM
Hi,
(I have removed any business specific information from the markup and code samples)
I have a problem with binding to a hierarchical data structure. The structure for the grid is following:
<telerik:RadGrid ID="RadGridExample" runat="server" GridLines="Both" Skin="WebBlue">
        <MasterTableView AutoGenerateColumns="false" Name="Main"> <%-- This level holds items of Type1 --%>
            <Columns>
                 
            </Columns>
            <DetailTables>
                <telerik:GridTableView Name="SubLevel1" ShowHeadersWhenNoRecords="false" AutoGenerateColumns="false"> <%-- This level holds items of Type2 --%>
                    <Columns>
                         
                    </Columns>
                    <DetailTables>
                          <telerik:GridTableView Name="SubLevel2" ShowHeader="false" AutoGenerateColumns="false" > <%-- This level holds items of Type2 --%>
                            <Columns>
                                 
                            </Columns>
                            <DetailTables>
                                <telerik:GridTableView Name="SubLevel3" ShowHeader="false" AutoGenerateColumns="false"> <%-- This level holds items of Type2 --%>
                                    <Columns>
                                         
                                    </Columns>
                                    <DetailTables>
                                        <telerik:GridTableView Name="SubLevel4" ShowHeader="false" AutoGenerateColumns="false"> <%-- This level holds items of Type2 --%>
                                            <Columns>
                                                 
                                            </Columns>
                                        </telerik:GridTableView>
                                    </DetailTables>
                                </telerik:GridTableView>
                            </DetailTables>
                        </telerik:GridTableView>
                    </DetailTables>
                </telerik:GridTableView>
            </DetailTables>
        </MasterTableView>
    </telerik:RadGrid>
Type1 and Type2 naturally have different properties.

For all levels I use custom sorting, custom paging and custom filtering. I get all data for the entire grid (all sub levels included) in the Page_PreRender stage (of the UserControl the Grid is in).

The problem is that I have not been able to get the detail grids to bind at all after I expand a row.

Here is a little bit of background information before I go on:
The life cycle of the control is following:
1. At Init (of the user control) the user control registeres it to the page. The user control implements a custom IView interface and the Page implements a custom IViewContiner interface. The code for this is following:
protected void Page_Init(object sender, EventArgs e)
{
    IViewContainer container = this.Page as IViewContainer;
    if (container != null)
    {
        container.Register(this);
    }
}

2. In the the containing Page's Page_LoadComplete the Page calls all its IView (user control) objects Initialize method where the user control collects its state information and sends it to an application controller. At this stage the user control should traverse the grid structure and get all expanded rows. From this information the back end can get all the information needed to get the data for the view(s) (user control(s)).

3. At user control's Page_PreRender the user control receives the data it is supposed to bind to the grid. The hierarchical data structure could be of type:
IEnumerable<Tuple<Type1,IEnumerable<Tree<Type2>>>>
Where the Type1 objects are objects that should be bound to the MasterTableView level and the IEnumerable of trees should be bound to the detail tables.

The Tree and its TreeNode classes look like this:
public class Tree<TItem>
{
 
    public Tree(TItem rootItem)
    {
        root = new TreeNode<TItem>(rootItem);
    }
 
    public Tree(TreeNode<TItem> rootNode)
    {
        this.root = rootNode;
    }
 
    private TreeNode<TItem> root;
 
    public TreeNode<TItem> Root
    {
        get { return root; }
    }
}
 
public class TreeNode<TItem>
{
 
    public TreeNode(TItem item)
    {
        this.item = item;
        this.children = new List<TreeNode<TItem>>();
        this.isLeaf = true;
    }
 
    public TreeNode(TItem item, bool isLeaf)
    {
        this.item = item;
        this.isLeaf = isLeaf;
        this.children = new List<TreeNode<TItem>>();
    }
 
    public TreeNode(TItem node, IEnumerable<TItem> children)
    {
        this.item = node;
        this.children = new List<TreeNode<TItem>>(children.Count());
        foreach (var child in children)
        {
            this.children.Add(new TreeNode<TItem>(child));
        }
        this.isLeaf = false;
    }
 
    private TItem item;
 
    public TItem Item
    {
        get { return item;}
    }
 
    private List<TreeNode<TItem>> children;
 
    public IEnumerable<TreeNode<TItem>> Children
    {
        get
        {
            if (isLeaf)
                throw new InvalidOperationException("Node is set as Leaf so it can't have children");
            return children;
        }
    }
 
 
    private bool isLeaf;
 
    public bool IsLeaf
    {
        get { return isLeaf; }
        set { isLeaf = value; }
    }
         
 
    public void AddChild(TItem item)
    {
        isLeaf = false;
        children.Add(new TreeNode<TItem>(item));
    }
 
    public void AddChild(TreeNode<TItem> item)
    {
        isLeaf = false;
        children.Add(item);
    }
 
    public void AddChildren(IEnumerable<TItem> items)
    {
        isLeaf = false;
        foreach (var item in items)
        {
            children.Add(new TreeNode<TItem>(item));
        }
    }
 
    public void AddChildren(IEnumerable<TreeNode<TItem>> items)
    {
        isLeaf = false;
        foreach (var item in items)
        {
            children.Add(item);
        }
    }
 
}

Now back to the problem at hand. I tried to bind the MasterTableView to the IEnumerable<Type1>:
RadGridExample.MasterTableView.DataSource = hierarchy.Select(tuple => tuple.Item1); Where hierarchy is the IEnumerable<Tuple<Type1,IEnumerable<Tree<Type2>>>>.

Then in the RadGridExample_DetailTableDataBind handler I Tried the following:
object parentDataItem = e.DetailTableView.ParentItem.DataItem;
if (parentDataItem is Type1)
{
    Type1 fac = parentDataItem as Type1;
    var tuple = hierarchy.Single(t => t.Item1 == fac);
    if (tuple.Item2 != null)
    {
        var roots = tuple.Item2.Select(tree => tree.Root);
        //selects IEnumerable<TreeNode<Type2>>
        e.DetailTableView.DataSource = roots;
    }
 
}
else if (parentDataItem is TreeNode<Type2>)
{
    TreeNode<Type2> node = parentDataItem as TreeNode<Type2>;
    if (!node.IsLeaf)
    {
        e.DetailTableView.DataSource = node.Children;
    }
}

The rules for binding are following (as the code above suggests):
 - If the IEnumerable<Type2> associated with the Type1 in the Tuple is null, then the row is not expanded and its detail table should not be bound to anything.
 - If the TreeNode<Type2> object has IsLeaf == true then it is not expanded and its detail table should not be bound to anything.

Is it possible to bind this kind for data structures the grid hierarchy all at once at Page_PreRender? The self referencing hierarchy is out of the question since I want to some day have custom paging on all levels of the hierarchy separately. For now I would settle for getting the data binding working.

I attached an example picture of the kind of data that the grid should bind to.

EDIT:

Some more issues I ran into:

Issue1:

If I set the data source of the grid or grid's master table view as IEnumerable<Type1> the Grid_ItemDataBound handler tries to bind the Type1 objects also to the DetailTable levels. This is not at all what I intended to do. The IEnumerable<Type1> objects should be bound to the master table view level (outer most level) and the trees should be bound to the detail table levels.
For example If my data source for the master table view level would be following:
{item1, item2, item3, item4}
The grid tries to create the following hierarchy from this list:
item1
 -Item1
 -Item2
 -Item3
 -Item4
Item2
 -Item1
 -Item2
 -Item3
 -Item4
...
And so on
So it is repeating the main level objects as the children of the main level objects.

Issue2:

I got the hierarchy expanding but it is super slow. Now the problem is that if I want to collapse any row the grid won't event make a request to the server. Although this only happens when the web app is run on the Visual Studio development web server. On IIS the collapse seems to work.

2 Answers, 1 is accepted

Sort by
0
Heikki Heikkinen
Top achievements
Rank 1
answered on 25 Oct 2010, 06:46 AM
I think I have solved my own problem. So to answer to my own question.
The problem was mostly HierarchyLoadMode="ServerBind". ServerBind wasn't at all suitable for this situation. Instead ServerOnDemand seems to work a lot better.

The key was to naturally set ServerOnDemand to all levels, then databind the top level IEnumerable to MasterTableView.DataSource and then work through the grid hierarchy side by side with the data hierarchy and set Expand to true to all nodes that were not leaf nodes. This way DetailTableDataBind worked as I wanted it to work.

I still don't fully understand how the ServerBind HierarchyLoadMode works. It would seem that it wants the whole hierarchy of data in one (non-generic) list (e.g. ArrayList) and tries to populate the whole hierarchy using that. I assume that this approach would require the DataKeyNames property to be set properly as well as the ParentTableRelations. I did not however get this to work. And populating the whole hierarchy from one list sounds like a costly operation as it seemed to be judging from the lack of speed when I tried to use server bind. I have 4-level hierarchy in my grid so populating the whole hierarchy from one list seems like a O(n^4) operation. Correct me if I'm wrong.
0
Radoslav
Telerik team
answered on 26 Oct 2010, 08:55 AM
Hello Heikki,

I am glad that you solved the described issues.

With HierarchyLoadMode.ServerBind, all child GridTableViews will be bound immediately when DataBind occurs for a parent GridTableView or RadGrid. The ViewState holds all detail tables data. However only the detail table-views of the expanded items are rendered.
If the HierarchyLoadMode is set to ServerOnDemand the child datatables are populated after the expand button is clicked. Initially the RadGrid loads only one level of its hierarchy and all items have not child DetailTables with child items.
When HierarchyLoadMode property is set to Client the RadGrid renders all its items to the client. Note that this will produce large amount of html and javascript and can result in significant delays in the grid loading time.

Also for the best performance I suggest you to use the Hierarchical data-binding with  DetailTableDataBind event and HierarchyLoadMode set to ServerOnDemand. In this way you could bind every level of the hierarchy to the subset of the data instead of binding the hole gird to complex collection of objects.

In case you experience any further problems, do not hesitate to contact us again.

All the best,
Radoslav
the Telerik team
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
Tags
Grid
Asked by
Heikki Heikkinen
Top achievements
Rank 1
Answers by
Heikki Heikkinen
Top achievements
Rank 1
Radoslav
Telerik team
Share this question
or