Click here to Skip to main content
Click here to Skip to main content

Optimizing building trees from a database

By , 20 Jan 2005
 

Introduction

Some time ago, I had to write some code that created an object tree from a DataView. Pretty straight forward job, the code worked and I didn't bother with it for a while.

Then recently I started using the ANTS Profiler and found that the tree construction was actually rather slow. I examined my old code again and came up with a way of speeding it up significantly...

This article explains how the recursive character of the DataView can be used to get a big performance improvement.

Note: the method I present doesn't rely on incremental sorting of the DataView. Sorted backwards or even a mess will work. This is a very welcome property if the tree data is from an external, uncontrollable source.

What's in the ZIP?

The ZIP-file contains the tree building library code, and the test application code. Also included is the tree DataSet in XML format. The tree consists of 745 nodes, going about five levels deep. The test application is a simple console application that creates TreeNodes using the slow and the fast way and shows the loading time in seconds. It does this five times to get a better idea of the performance boost.

From the tree's point of view

In my original code, I handled the problem from the tree's point of view. The tree always has a root node, so I started with that node, and searched for its child nodes in the DataView. For every child node, I searched for their respective child nodes as well, and so on.

The DataSet consists of a collection of records with three columns: NodeID, ParentNodeID and NodeText. The data is recursive: The ParentNodeID refers to the NodeID of the parent node. The database makes sure that a node can't be attached to a non-existent parent node. Because of this rule, a root node is made by having the node point to itself.

  public class TreeNode
  {
    // Declarations
    private int id;               // The ID of the node
    private TreeNode parentNode;  // The parent node of the node
    private string text;          // The payload of the node
    private ArrayList childNodes; // Contains the childnodes of the node

    // Constructor
    public TreeNode(int id, TreeNode parentNode)
    {
      // Initialize the FastTreeNode
      this.id = id;
      this.parentNode = parentNode;
      this.text = "";
      childNodes = new ArrayList();

      // Check if a parent was supplied
      if (parentNode != null)
      {
        //Yes, then let the parentnode know it has a new child
        parentNode.ChildNodes.Add(this);
      }
    }

    // Properties
    public int ID
    {
      get {return id;}
    }

    public TreeNode ParentNode
    {
      get {return parentNode;}
      set
      {
        parentNode = value;
        // Let the parent node know it has a new child,
        // if possible, and if necessary
        if (parentNode != null && !parentNode.ChildNodes.Contains(this))
          parentNode.ChildNodes.Add(this);
      }
    }

    public string Text
    {
      get {return text;}
      set {text = value;}
    }

    public ArrayList ChildNodes
    {
      get {return childNodes;}
    }
  }

The TreeNode class basically reflects the database structure. It has an ID, and it refers to a parent. A root node has a null parent. As a simple payload, the node has a Text property.

public TreeNode LoadTree(DataView dataTree)
    {
      // Prepare the resulting rootnode
      TreeNode rootNode = null;

      // Loop through the dataview
      foreach (DataRowView dataNode in dataTree)
      {
        // Store the current record in typed variables
        int nodeID = (int)dataNode["NodeID"];
        int parentNodeID = (int)dataNode["ParentNodeID"];
        string text = dataNode["NodeText"].ToString();

        // Check if the current node is the rootnode
        if (nodeID == parentNodeID)
        {
          // Yes, so create the node, and start
          // searching for it's child nodes
          rootNode = new TreeNode(nodeID, null);
          rootNode.Text = text;
          LoadChildNodes(rootNode, dataTree);
        }
      }

      // Return the result
      return rootNode;
    }

    private void LoadChildNodes(TreeNode parentNode, DataView dataTree)
    {
      // Loop throught the dataview
      foreach (DataRowView dataNode in dataTree)
      {
        // Store the current record in typed variables
        int nodeID = (int)dataNode["NodeID"];
        int parentNodeID = (int)dataNode["ParentNodeID"];
        string text = dataNode["NodeText"].ToString();

        // Check if the current node is not the root node,
        // and if the current node
        // is a child node of the supplied parent node
        if (nodeID != parentNodeID && parentNodeID == parentNode.ID)
        {
          // Yes, create the node
          // and start searching for it's child nodes.
          TreeNode node = new TreeNode(nodeID, parentNode);
          node.Text = text;
          LoadChildNodes (node, dataTree);
        }
      }
    }

The LoadTree method calls the recursive LoadChildNodes method. This makes the code quite compact, however, there's one big problem with it: for every node, it scans the entire DataView, including already handled nodes. So with the test DataSet, 555770 records will be read. Combined with the fact that the DataView doesn't read too fast, the total time required to place all nodes is considerable.

Also, if you try to load a very deeply branched tree, it might cause stack overflow problems.

From the DataView's point of view

The DataView has a linear character: it starts at record 0 and ends at record x. Just going through the records from 0 to x to create each node doesn't seem possible at first: if its parent node was not created earlier, it's impossible to create the current node. Or is it?

The trick is to let go of the idea that each object you create should be complete. I don't know anything about the parent node, but I do know the ID it will have. So I can create an new node with only the parent's ID and no parent node, and store it in a Hashtable. If I find the ID of the empty node later on in the DataSet, I don't create it but get the stored node from the Hashtable instead and fill in the empty properties.

public TreeNode LoadTree(DataView dataTree)
    {
      // Loop through all records in the dataview
      foreach (DataRowView dataNode in dataTree)
      {
        // Store the current record in some typed variables
        int nodeID = (int)dataNode["NodeID"];
        int parentNodeID = (int)dataNode["ParentNodeID"];
        string text = dataNode["NodeText"].ToString();

        // Check if the node was already created
        if (nodeList.Contains(nodeID))
        {
          // Yes, fill in the missing properties
          TreeNode node = (TreeNode)nodeList[nodeID];
          node.Text = text;
          // If the node is not a root node,
          // and there's no parent yet, look up or create
          if (nodeID != parentNodeID && node.ParentNode == null)
          {
            TreeNode parentNode = null;
            // Check if the parentnode was already created
            if (nodeList.Contains(parentNodeID))
            {
              // Yes, so use that one
              parentNode = (TreeNode)nodeList[parentNodeID];
            }
            else
            {
              // The parentnode doesn't exist yet, so create a partial parentnode.
              parentNode = new TreeNode(parentNodeID, null);
              nodeList.Add(parentNodeID, parentNode);
            }
            node.ParentNode = parentNode;
          }
        }
        else
        {
          // New node, check if it's a root node
          if (nodeID == parentNodeID)
          {
            // Yes, so no need for looking up or creating a parentnode
            TreeNode node = new TreeNode(nodeID, null);
            node.Text = text;
            nodeList.Add(nodeID, node);
          }
          else
          {
            // New child node
            TreeNode parentNode = null;
            // Check if the parentnode was already created
            if (nodeList.Contains(parentNodeID))
            {
              // Yes, so use that one
              parentNode = (TreeNode)nodeList[parentNodeID];
            }
            else
            {
              // No, so create a partial parentnode.
              parentNode = new TreeNode(parentNodeID, null);
              nodeList.Add(parentNodeID, parentNode);
            }

            // Create the new node
            TreeNode node = new TreeNode(nodeID, parentNode);
            node.Text = text;
            nodeList.Add(nodeID, node);
          }
        }
      } 
     
      // Find the rootnode
      IDictionaryEnumerator nodeEnumerator = nodeList.GetEnumerator();
      while (nodeEnumerator.MoveNext())
      {
        TreeNode node = (TreeNode)nodeEnumerator.Value;
        if (node.ParentNode == null)
          return node;
      }

      // Return nothing if no rootnode was found
      return null;
    }

The LoadTree method here is a bit longer but it reads every record only once. Instead of searching nodes in the DataView, they're searched using the much faster Hashtable. The only additional thing that needs to be done at the end is locating the root node, whereas the SlowTree version has it ready at hand: it was the starting point. However, the extra time this extra code requires isn't really noticeable because of the speed with which the tree was built.

The results

On my 1.8GHz laptop, the test application requires just a little above two seconds to get the tree using the slow method. Using the fast method, it only takes about 0.004 seconds to create the same tree. That's about 500 times faster!

Note: of course, the test tree is a very simple tree. In real-life applications, each tree node might have a bigger payload than just a text. This will probably slow it down a little, but it will still be much faster than the original slow tree. I've seen improvements up to 400 times faster in my applications.

Conclusion

Applications can have a bad performance because:

  • it calls (fast) methods very often, or
  • it calls methods that are very slow, or, worst of all,
  • it calls methods that are very slow very often.

The solution offered in this article avoids this by keeping the calls to the slowest method (reading records from a DataView) to an absolute minimum. Admittedly, this is only possible because of the close relationship between the child- and parent objects, and some clever using of the little information we have about the objects.

Acknowledgements

I'd like to thank:

  • Daniel Strigl for his easy-to-use HighPerformanceTimer class[^] , which I used to take the measurements.
  • My colleague Jan, who originally came up with the described idea.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

NielsHoldijk
Software Developer
Netherlands Netherlands
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralSlow Tree Can Approach Fast Treememberzaighum8-Apr-05 21:50 
I think slow tree can approach to fast tree if you dont pass through all nodes but only child nodes.If you use filter before searching then theres no big difference...
GeneralFaster SlowTree with DataTable.Selectmembertw6531-Jan-05 0:46 
Your SlowTree is realy slow Smile | :)
Starting the test...
SlowTree: 1,40959060439246
FastTree: 0,00388066081024264
SlowTree: 1,44099208139582
FastTree: 0,00261122572840962
SlowTree: 1,40443464183297
FastTree: 0,00406448305580737
SlowTree: 1,42344348234203
FastTree: 0,00319314326262137
SlowTree: 1,41355256045112
FastTree: 0,00258608286807402
Press enter to continue...
 
My SlowTree is faster
Starting the test...
SlowTree: 0,0713822566834612
FastTree: 0,0060739563268516
SlowTree: 0,0322873437825198
FastTree: 0,00300876228682696
SlowTree: 0,0326753819270326
FastTree: 0,00305541626100524
SlowTree: 0,0469537329465058
FastTree: 0,00263469239805618
SlowTree: 0,032997769269558
FastTree: 0,00299283847528108
Press enter to continue...
 

change the change the call in Class1.cs Line 34 to
 
TreeNode slowRoot = slowTree.LoadTree(data.Table);
 
and merge code in SlowTree.cs
 
public TreeNode LoadTree(DataTable dataTree)
{
// Prepare the resulting rootnode
TreeNode rootNode = null;
 
// Loop through the dataview
foreach (DataRow dataNode in dataTree.Select("NodeID = ParentNodeID"))
{
// Yes, so create the node, and start searching for it's child nodes
rootNode = new TreeNode((int)dataNode["NodeID"], null);
rootNode.Text = dataNode["NodeText"].ToString();
LoadChildNodes(rootNode, dataTree);
}
 
// Return the result
return rootNode;
}
 
private void LoadChildNodes(TreeNode parentNode, DataTable dataTree)
{
// Loop throught the dataview
foreach (DataRow dataNode in dataTree.Select("ParentNodeID ='" + parentNode.ID + "'"))
{
// Store the current record in typed variables
int nodeID = (int)dataNode["NodeID"];
int parentNodeID = (int)dataNode["ParentNodeID"];
string text = dataNode["NodeText"].ToString();
 
// Check if the current node is not the root node, and if the current node is a child node of the supplied parent node
if (nodeID != parentNodeID && parentNodeID == parentNode.ID)
{
// Yes, create the node and start searching for it's child nodes.
TreeNode node = new TreeNode(nodeID, parentNode);
node.Text = text;
LoadChildNodes (node, dataTree);
}
}
}
 
viele Grüße aus Berlin
Thomas

GeneralFasterTreememberNewKidX28-Jan-05 2:11 
Hi,
You could gain even more performance if you separate the code that creates the nodes from the code that lays out the structure of the tree. In the first pass you create the nodes straight and store them into an array (to preserve the order from the datasource) as well as a hashtable (for futrther parent lookups). In the second pass iterate over the array and for each node there find it's matching parent using the hashtable. The only addition you need is to add ParentId property to the TreeNode class.
 
public class FasterTree
{
	private Hashtable nodeHash;
	private TreeNode[] nodeList;
 
	public TreeNode LoadTree(DataView dataTree)
	{
		TreeNode root = null;
		nodeHash = new Hashtable(dataTree.Count);
		nodeList = new TreeNode[dataTree.Count];
 
		// first pass: create nodes
		for (int i = 0; i < dataTree.Count; i++)
		{
			DataRowView dataNode = dataTree[i];
 
			// get the details
			int nodeID = (int)dataNode["NodeID"];
			int parentNodeID = (int)dataNode["ParentNodeID"];
			string text = dataNode["NodeText"].ToString();
 
			// create the node (no parentNode yet)
			TreeNode node = new TreeNode(nodeID, null);
			node.ParentID = parentNodeID; // we'll need this in the second pass
			node.Text = text;
 
			// store it in the list and the hashtable
			nodeHash.Add(nodeID, node);
			nodeList[i] = node;
		}
 
		// second pass: iterate over the list and link the nodes with their parents
		// will find the root node along the way
		for (int i = 0; i < nodeList.Length; i++)
		{
			TreeNode node = nodeList[i];
			if (node.ID == node.ParentID)
				root = node;
			else
				node.ParentNode = (TreeNode)nodeHash[node.ParentID];
		}
		return root;
	}
}
 
The effect is that you do the necessary minimum of parent lookups (only one for each node, always hits). Also the code is simpler. And it's indeed faster. The only drawback might be the slightly increased memory footprint.
 
Cheers,
NewKid.
GeneralRe: FasterTreememberNielsHoldijk29-Jan-05 6:16 
I tested your version, and it's faster indeed. I'd like to comment about the reason for using the array though: Rather than for preserving the original order (not necessary), I'd use it's speed advantage over the Hashtable as a argument.
 
There's one thing I'm wondering about, and it may be a bit far fetched. What if the 'parent must exist' constraint doesn't exist in the datasource? Ugly, but possible. The parent lookup in the second pass would fail because the requested key doesn't exist in the hashtable. Easily fixed with a Contains check, but there is a difference with the FastTree implementation then.
 
The FastTree would create valid, complete treenodes pointing to rootnodes. The FasterTree would create the same treenode as a rootnode itself.
 
I'm inclined to think that the FastTree is more accurate in this case: if the node says it has a parent, it can't possibly be a rootnode.
 
Of course this would only matter if not one rootnode was returned, but all rootnodes instead. I guess this would be something like a trunkless tree.
 
Perhaps things like pathfinding algorithms have this problem?
 
Niels
GeneralLoad them dynamiclymemberD4Skunk20-Jan-05 20:58 
Why do you need to load all the treenodes at once ? I suppose a user doesn't need them all every time he/she uses the prog.
Add a dummy treenode to each visible node, delte the dummy and load the children when the node is opened.
Not only will your loading be faster, but you will also avoid problematic memory usage...
 
Tom
 
Core
GeneralRe: Load them dynamiclymemberNielsHoldijk20-Jan-05 21:59 
I used the tree in a web page. Because reloading the page every time a user opens a node would make it really slow I decided to put the whole thing in layers. And then you need the entire tree...
 
Also, to replace the dummy child node with the actual children you'd need to scan the data again. I think that would slow it down actually. The only thing I can imagine is that the slowness of the tree will be spread over every clicked node, instead of over the entire tree at once. This might be less noticable to the user, I guess.
 
Niels
GeneralAn even faster waymemberSébastien Lorion20-Jan-05 18:10 
If the childID is always greater than the parentID, you can simply sort the view on the ID column and then fill the tree, with the first row being the root.
 
Sébastien
 


Intelligence shared is intelligence squared.
 
Homepage : http://sebastienlorion.com
GeneralRe: An even faster waymemberNielsHoldijk20-Jan-05 21:42 
I agree, but it's not always possible to guarantee that the database will store the nodes in the proper order. Also, by sticking to that rule it would not be possible to move nodes from one parent to another parent if that other parent has a higher ID than the child ID.
 
Niels
GeneralRe: An even faster waymemberSébastien Lorion21-Jan-05 3:11 
Hence the starting if of my comment. In my experience, users can rarely move nodes around, they usually only renames, adds or removes them. And as long as the ID column is a generated sequence, you are in business.
 
Also, I would suggest that you use a IDataReader if you can.
 
With the code you present, you will have 3 full copies of the data at the end. Using a data reader, you will save one and if pre-sorting the nodes is possible, you will save another.
 
Sébastien
 


Intelligence shared is intelligence squared.
 
Homepage : http://sebastienlorion.com
GeneralRe: An even faster waymemberNielsHoldijk22-Jan-05 0:59 
With your suggestion in mind I've created a 'SortedTree' without a lookup Hashtable to compare it. It is indeed faster, but with only about 1 or 2 thousands of a second. I expect the memory usage to be lower as well, although very little because, if I'm not mistaken, the FastTree's hashtable only stores very compact references instead of the actual node. Oh, and the SortedTree code is shorter as well.
 
If the tree is pre-sorted both methods actually work pretty much the same, because the FastTree method will never have to create a unknown parent. I think the speed difference is in the extra loop at the end of the FastTree code to find the root node. The SortedTree doesn't need it because it started with that root node.
 
To prevent undermining my own article, I also shuffled the xml file a bit. Both the SlowTree and the FastTree still came up with the root node, whereas the SortedTree only came up with 'null reference' exceptions.
 
So I'll stick to my FastTree. I'm happy to pay 0.002 seconds for reliability Smile | :) Especially when you get the tree data from an external system where you can't control the contents, I don't think that the extra safety is a luxury...
 
I'll add a note to the article that it also works with messed up trees.
 
Niels
GeneralRe: An even faster waymemberSébastien Lorion22-Jan-05 10:15 
1-2 ms for a total of what ? It's the ratio that matters.
 
The xml file contains only 750 nodes. You can compare BubbleSort and QuickSort for N=10 and call them even, but now do it for N=1000000.
 
Anyway, your function is already linear in time and space so you won't see any big improvements no matter how hard you try. The SlowTree was in O(N^2), so that's why you got such a speed gain even with a low node count. I did write my comment a bit fast because you are right about the HashTable memory footprint.
 
For the kick of it, I wrote a quick implementation of the so-called SortedTree. One which uses a DataView and another which uses a IDataReader. I compared both to FastTree with a set of ~400000 nodes generated from the files on my drives. I included the LoadData() method in the benchmark because I sorted the nodes directly on the DB with an ORDER BY clause.
 
With DataView, I get ~10% speed gain and no significant memory gain. With DataReader, I get ~20% speed gain and ~20% memory gain. Not a lot, but still a nice improvement.
 
Sébastien
 


Intelligence shared is intelligence squared.
 
Homepage : http://sebastienlorion.com
GeneralRe: An even faster waymemberNielsHoldijk23-Jan-05 3:38 
The 1-2 ms was compared to the FastTree. With the FastTree getting 6-7 ms (it performed a little slower with the same code and data Confused | :confused: Probably some other process hogging the CPU at the time) that would mean a 30% gain. I didn't test with such huge tree but 30% would be noticable then, I admit.
 
The DataReader implementation should be faster because it cuts out the middle man. I'd like to note that there is a (small) catch with it. 2 DataReaders on a single connection won't work. So every active DataReader should have it's own exclusive connection. If multiple users execute the same code at the same time you might run out of connections. We had problems with that in our web applications, and decided to write a database handler which returns dataviews to be able to release the connection as soon as possible.
 
I realize that the FastTree is a bit overkill if the tree data can be sorted. Could you send me your 'SortedTree' implementation? I'd like to compare it to my implementation and then include it in the article, with the FastTree as an alternative for messy tree data.
 
Niels
GeneralRe: An even faster waymemberAshaman26-Jan-05 3:18 
This thread was a very interesting read. In my experience, any tree that could be guaranteed to be sortable, with ParentID's always being lower in value than the ID of their children, evenually could not be so guaranteed. I have written a similar tree population mechanism that initially sorts by partentid and childid, in case I'm lucky, but then keeps track of those nodes that may need to be reattempted to be inserted into the tree after the first pass. I will change this particular part to use your partially populated node hashtable instead because that's a great idea.
 
The only other thing my population method allows is the text of a node to hold those records that cannot be inserted into the tree because their parent is non-null and is not otherwise in the dataset. This should never happen but not every project gets the same attention to referential integrity, so I like to help my apps help me to discover issues.
 
Great article.
 
-Kevin Buchan
GeneralRe: An even faster waysussGWSyZyGy28-Jan-05 4:00 
I agree that faster is not always better. Reliability is far more important.
 
There are two additional problems to contend with when building trees -- one is the possibility of having orphan branches in the data (missing parent), and the second is the possibility of circular references (NodeA -> NodeB -> NodeA). Without testing, I'd bet the "slow" tree method would either blow up with null reference exceptions, or run forever and ever and ever .... On the other hand, both the "fast" and the "sorted" methods have a chance of handling the problems (since they iterate the data just once), but the "fast" method will likely handle them better since each node is not required to have a parent at creation. The "sorted" method expects the parent node to already exist.
 

GeneralRe: An even faster waymemberSébastien Lorion31-Jan-05 18:06 
Orphan branches should lead to a new root since the definition of a root is a node with no parent.
 
Also, by definition a tree does not contain cycle, otherwise it is a graph which is quite different.
 
Sébastien
 


Intelligence shared is intelligence squared.
 
Homepage : http://sebastienlorion.com
GeneralRe: An even faster waymemberSébastien Lorion31-Jan-05 18:12 
It all depends what you mean by reliability. Do you want your application to mask potential database integrity failures or to output an error when it encounters corrupted data. Sure thing, if you know your input might need some correction, use whatever means it takes to handle exceptions, but otherwise, assert that the data is well formed (programming by contract).
 
Sébastien
 


Intelligence shared is intelligence squared.
 
Homepage : http://sebastienlorion.com
GeneralRe: An even faster waymemberSébastien Lorion31-Jan-05 18:25 
Sorry for the delay ... Here is the code :
 
public static TreeNode LoadTree(DataView dataTree, string idColumn, string parentColumn, string dataColumn, string sort)
{
	if (dataTree == null)
		throw new ArgumentNullException("dataTree");
 
	if (dataTree.Table == null)
		throw new ArgumentException("No data available (DataView.Table property is null).", "dataTree");
 
	if (idColumn == null)
		throw new ArgumentNullException("idColumn");
 
	if (parentColumn == null)
		throw new ArgumentNullException("parentColumn");
 
	if (dataColumn == null)
		throw new ArgumentNullException("dataColumn");
 
	return LoadTree(dataTree, dataTree.Table.Columns.IndexOf(idColumn), dataTree.Table.Columns.IndexOf(parentColumn), dataTree.Table.Columns.IndexOf(dataColumn), sort);
}
 
public static TreeNode LoadTree(DataView dataTree, int idColumn, int parentColumn, int dataColumn, string sort)
{
	if (dataTree == null)
		throw new ArgumentNullException("dataTree");
 
	if (dataTree.Table == null)
		throw new ArgumentException("No data available (DataView.Table property is null).", "dataTree");
 
	if (idColumn < 0)
		throw new ArgumentOutOfRangeException("idColumn", idColumn, "Must be a positive integer.");
 
	if (parentColumn < 0)
		throw new ArgumentOutOfRangeException("parentColumn", parentColumn, "Must be a positive integer.");
 
	if (dataColumn < 0)
		throw new ArgumentOutOfRangeException("dataColumn", dataColumn, "Must be a positive integer.");
 

	if (sort != null && sort != "" && dataTree.Sort != sort)
		dataTree.Sort = sort;
 
	Hashtable nodeList = null;
	TreeNode root = null;
 
	if (dataTree.Count > 0)
	{
		DataRowView row = dataTree[0];
 
		root = new TreeNode((int) row[idColumn], null);
		root.Text = row[dataColumn].ToString();
	}
 
	if (dataTree.Count > 1)
	{
		nodeList = new Hashtable();
		nodeList.Add(root.ID, root);
	}
 
	for (int i = 1; i < dataTree.Count; i++)
	{
		DataRowView row = dataTree[i];
 
		int ID = (int) row[idColumn];
		object parentID = row[parentColumn];
		string data = row[dataColumn].ToString();
 
		TreeNode parentNode = (TreeNode) nodeList[parentID];
 
		if (parentNode == null)
			throw new InvalidOperationException(String.Format("Cannot find parent with ID = '{0}'.", parentID));
 
		TreeNode node = new TreeNode((int) ID, parentNode);
		node.Text = data.ToString();
 
		nodeList.Add(ID, node);
	}
 
	return root;
}
 
public static TreeNode LoadTree(IDataReader dataTree, string idColumn, string parentColumn, string dataColumn)
{
	if (dataTree == null)
		throw new ArgumentNullException("dataTree");
 
	if (idColumn == null)
		throw new ArgumentNullException("idColumn");
 
	if (parentColumn == null)
		throw new ArgumentNullException("parentColumn");
 
	if (dataColumn == null)
		throw new ArgumentNullException("dataColumn");
 
	return LoadTree(dataTree, dataTree.GetOrdinal(idColumn), dataTree.GetOrdinal(parentColumn), dataTree.GetOrdinal(dataColumn));
}
 
public static TreeNode LoadTree(IDataReader dataTree, int idColumn, int parentColumn, int dataColumn)
{
	if (dataTree == null)
		throw new ArgumentNullException("dataTree");
 
	if (idColumn < 0)
		throw new ArgumentOutOfRangeException("idColumn", idColumn, "Must be a positive integer.");
 
	if (parentColumn < 0)
		throw new ArgumentOutOfRangeException("parentColumn", parentColumn, "Must be a positive integer.");
 
	if (dataColumn < 0)
		throw new ArgumentOutOfRangeException("dataColumn", dataColumn, "Must be a positive integer.");
 
	Hashtable nodeList = new Hashtable();
	TreeNode root = null;
 
	if (dataTree.Read())
	{
		root = new TreeNode((int) dataTree[idColumn], null);
		root.Text = dataTree[dataColumn].ToString();
		nodeList.Add(root.ID, root);
	}
 
	while (dataTree.Read())
	{
		int ID = (int) dataTree[idColumn];
		object parentID = dataTree[parentColumn];
		string data = dataTree[dataColumn].ToString();
 
		TreeNode parentNode = (TreeNode) nodeList[parentID];
 
		if (parentNode == null)
			throw new InvalidOperationException(String.Format("Cannot find parent with ID = '{0}'.", parentID));
 
		TreeNode node = new TreeNode((int) ID, parentNode);
		node.Text = data.ToString();
 
		nodeList.Add(ID, node);
	}
 
	return root;
}
 
If you can obtain the level of the node (using LEVEL keyword in Oracle for example, see http://download-west.oracle.com/docs/cd/B10501_01/server.920/a96540/sql_elements6a.htm#9547[^]), you can also make further optimizations, like using a Queue instead of a Hashtable, hence minimizing even more the memory requirement.
 
Sébastien
 


Intelligence shared is intelligence squared.
 
Homepage : http://sebastienlorion.com
GeneralRe: An even faster waymemberTheLionClaws3-Aug-05 12:10 
What's the point of this crap without a treeview control. This console is crap.
GeneralTestsussBringerOfRocketyDeath20-Jan-05 12:20 
Test

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130619.1 | Last Updated 20 Jan 2005
Article Copyright 2005 by NielsHoldijk
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid