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

Hierarchical Tree Represented by Modified Preorder Tree Traversal Technique using C# 3.0 and SQL 2005.

By , 10 Dec 2008
 
demosmall.JPG

Introduction

During my work in a (CMS) Content Management System, and in the module of Content-mapping (which is a .NET desktop application) I needed to classify my contents into a chain of categories like hierarchical tree, but I also needed to represent that tree into more than a format, such as Textual, Tabular, Nodes and Database.

I searched the web about which .NET library is going to represent my tree into the SQL2005 database. I couldn't found that library, but I found an article (Gijs Van Tulder) is titled Storing Hierarchical Data in a Database that explains the concept of the MPTT (Modified Preorder Tree Traversal). Unfortunately, it was written for PHP developers, so, I translated the concept into C# and SQL2005 code. Moreover, I wrote some algorithms to represent trees into textual, tabular, tree view nodes and graphical format. I also used provider model techniques as a step to support various databases such as SQLite.

Finally, I gathered all these staff into one project, but, the entire classes could be easily reused and separated into class libraries, even the innovative algorithms (that I wrote) could be reused individually like the one that is written to render the graphical representation of the tree.

Now I think that I have an efficient way to add, insert, delete and retrieve tree-elements using many ways including a way for SQL2005 database with a very effective schema, which is MPTT that minimizes the number of database queries by having just one query for each activity.

What is Hierarchical Tree?

Simply, it is a structure of data that looks like a real tree, even though the hierarchical-tree is generally shown upside down compared with a real tree; that is to say with the root at the top and the leaves at the bottom, elements of that structure are related with each other by a parent-child relationship, those relationships are represented by a connecting-lines between elements and they are called "Branches", moreover, elements that have no superior are called "ROOTs" and elements that have no children are called "Leaves".

The following example demonstrates a textual representation for the Animal-kingdom tree:

Animal Kingdom
#Backbones
##Mammal
##Lungs
##Reptile
##Bird
##Gills
###Fish
###Amphibian
#No Backbones
##Starfish
##Mollusk
###Snail
###Clam
##Jointed Legs
###Insect
###Spider
###Crustacean
Figure 1: Hierarchical tree, textual representation.

So we have 1 root (Animal Kingdom) and 12 leaves (Mammal, Lungs, Reptile, Bird, Fish, Amphibian, Starfish, Snail, Clam, Insect, Spider, Crustacean), we have 18 lines where each line contains one tree element/node, the count of (#)s in front of the node-title represents the depth level of the element in the tree, and elements that have the same level and have the same parent are called "SIBLINGs".

A graphical representation of such trees exist in Figure 2, where a rectangle is a symbolic representation of a node, and also a tabular representation for such trees exist in Figure 3.

treesmall.JPG

Figure 2: Hierarchical tree graphical representation.
ID Parent ID Title Left Right
1 0 Animal Kingdom 1 36
2 1 Backbones 2 17
3 2 Mammal 3 4
4 2 Lungs 5 6
5 2 Reptile 7 8
6 2 Bird 9 10
7 2 Gills 11 16
8 7 Fish 12 13
9 7 Amphibian 14 15
10 1 No Backbones 18 35
11 10 Starfish 19 20
12 10 Mollusk 21 26
13 12 Snail 22 23
14 12 Clam 24 25
15 10 Jointed Legs 27 34
16 15 Insect 28 29
17 15 Spider 30 31
18 15 Crustacean 32 33
 
Figure 3: Hierarchical tree tabular representation.

Using the Demo Project

Back to the previous section, copy the text of Figure 1, and paste it into the TextBox of the right hand side on the interface of demo project, then, press the button that is titled "<<" to convert the text into the traditional tree representation in the TreeView control in the left hand side.

Show a tabular representation of the tree by clicking the radio button that is called "Tabular," and toggle between textual and tabular using the the other radio button that is called "Textual".

To save the tree you just converted into SQL2005 database you should first select an existing database, but you don't have to create any tables, just construct a working connection-string by clicking the button that is called "Connection String," after composing the connection string, simply click the button that is called "Save" to save the tree into a table called "tblTree," the table will be created automatically if it does not exist or is truncated if it is existing.

Of course, you could load this tree any time again from the same connection-string if you provided it correctly, just compose the connection string and click the "Load" button. However, you don't have to enter the connection string each time you open the demo project, it saves the connection string into a file named constring.txt automatically.

Now you can enjoy showing the graphical representation of the tree by clicking the "Draw" button.

Using the Code

You simply have to create an object from the MpttCoreEngine class and call any method from its interface.

MpttCoreEngine engine = new MpttCoreEngine();

This class contains all the algorithms that were written to represent hierarchical trees in various formats, the following list contains the most important methods in this class.

SetConnectionString Use this function to provide a connection string to the engine
ConvertMpttIntoTree Use this function to load tree nodes from the database
ConvertTreeIntoMptt Use this function to save tree nodes to database
ConvertTextIntoTree Converts textual tree into TreeNode suitable to TreeView
ConvertTextTableIntoTree Converts tabular tree into TreeNode suitable for TreeView
ConvertTreeIntoText Converts TreeNode back to textual representation
ConvertTreeIntoTable Converts TreeNode back to tabular representation
DrawTree Draws TreeNode on an Image and returns the Image for later use

The following code is a snapshot from the demo project which demonstrates how to use those methods.

	private void LoadDb()
	{
	    try
	    {
	        treeView.Nodes.Clear();
	        TreeNode node = null;
	        engine.SetConnectionString(_connectionString);
	        node = engine.ConvertMpttIntoTree();
	        if (node != null)
	        {
	            treeView.Nodes.Add(node);
	            treeView.ExpandAll();
	        }
	    }
	    catch (Exception e)
	    {
	        MessageBox.Show(e.Message +
                     "\r\nThe Connection string may not be correct");
	    }
	}
	
	private void SaveDb()
	{
	    if (treeView.Nodes.Count <= 0)
	        return;
	    try
	    {
	        engine.SetConnectionString(_connectionString);
	        engine.ConvertTreeIntoMptt(treeView.Nodes[0]);
	    }
	    catch (Exception e)
	    {
	        MessageBox.Show(e.Message +
                     "\r\nThe Connection string may not be correct");
	    }
	}
	
	private void TreeReflection()
	{
	    char t = (delimiter.Text.Length > 0 ? delimiter.Text[0] : '#');
	    engine.LevelDelimiter = t;
	    //
	    treeView.Nodes.Clear();
	    TreeNode node = null;
	    if (textual.Checked)
	        node = engine.ConvertTextIntoTree(textView.Text, _delimiter, '*');
	    else//tabular
	        node = engine.ConvertTextTableIntoTree(textView.Text, true);
	    if (node != null)
	    {
	        treeView.Nodes.Add(node);
	        treeView.ExpandAll();
	    }
	}
	
	private void TextReflection()
	{
	    if (treeView.Nodes.Count <= 0)
	        return;
	    char t = (delimiter.Text.Length > 0 ? delimiter.Text[0] : '#');
	    engine.LevelDelimiter = t;
	    if (textual.Checked)
	    {
	        textView.Text = engine.ConvertTreeIntoText(treeView.Nodes[0], false);
	    }
	    else
	    {
	        textView.Text = engine.ConvertTreeIntoTable(treeView.Nodes[0], false);
	    }
	}

	private void btnDraw_Click(object sender, EventArgs e)
	{
	    if (treeView.Nodes.Count <= 0)
	        return;
	    Bitmap bmp = engine.DrawTree(treeView.Nodes[0]);
	    TreePreview preview = new TreePreview(bmp);
	    preview.Show();
	}

Points of Interest

The code is full of interest points, where most of them may need individual articles to explain. Here are the most important:

  1. 1- The algorithm of drawing the tree nodes is so complex, I used anonymous functions, recursion and callbacks to calculate accurate locations for the nodes. You can find that algorithm on the function DrawTree.
  2. The MPTT concept is explained in several places on the web, though, I composed that concept with a design pattern known by "provider model", where, an abstract class called TreeProvider is created to be implemented and supports any database type like SQL2005, MySQL or SQLite.
  3. You could simply separate the core of the demo projects into a class library by copying only the folder named Core to any project you want.

History

Version 1.0:

  1. TreeView representation
  2. Textual representation
  3. Tabular representation
  4. MPTT representation
  5. Graphical representation
  6. SQL2005 support

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Wael Alghool
Architect Government
Qatar Qatar
Member
Programmer since 1990 with Pascal, VC++, C#, ASP.NET, jQuery, J2EE and Android.
PMP Certified since 2009.
PSP Certified since 2005.
Business & System analyst since 2004.
Led teams in between 8 to 30 members.
Worked for www.harf.com, www.islamweb.net, islam.gov.qa and islamonline.net.

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   
Questionwill it work for sqlite?memberlovingwind24 May '12 - 1:44 
I saw a class for sqlite, and I tried it , but there seems a lot problems of it. It loos like that your sql sentence is for sql server but not sqlite. Or maybe I did something wrong. Can you tell me how to use it for sqlite?
GeneralMy vote of 5memberwasiuddin13 Oct '11 - 8:07 
Really Very Very Marvellous Coding, and follow the structure. Thanks for Co-operation in this regard.
 
Wasiuddin Siddiqui
GeneralQuestionsmemberBBBrrriiiaaannn10 Oct '09 - 11:41 
Great code.
 
Are you going to add the method for adding and removing nodes?
Please post an example of adding a node, and removing a node, I assume a partial rebuild will be required for these operation.
 
Many Thanks!
Generaluse this code for Silverlight 3 TreeView via MVVMmemberGreg Hazzard31 Jul '09 - 2:32 
I am first trying to understand what you have before trying to integrate this with the concepts put forth by Josh Smith in:
 
Simplifying the WPF TreeView by Using the ViewModel Pattern[^]
GeneralIts a great technique, but...memberAdrian Schröder19 Jul '09 - 21:00 
be carefull of using it with big data. The big goal is, that you can find the data you
need in a very short time, but insert or delete data will need a lot of time.
 
I used it in a own system and i had terrible insert-times after 5000 rows.
 
But normally, for small systems it is a nice and mid easy technique
QuestionPlease advise me...memberDevil_Ashutosh16 Jun '09 - 0:11 
Dear Sir,
 
I am making the project in C# for showing "Hierarchical Data". As my need, i want to add/delete node at any level with the using of simple UI. According to our requirements, the number of nodes may be in the range of 50,00,000 to 60,00,000. Can I use your code in terms of performance & efficiency?
 
Can your code gives good result if node range goes to 50,00,000 to 60,00,000.
 
Please suggest us.
 

With Regards
Ashutosh
ashutosh.sgupta@yahoo.co.in
ashu_s_g@yahoo.co.in
AnswerRe: Please advise me...memberWael Alghool19 Jun '09 - 9:05 
Unfortunately, this version of the code is not optimized, but I have a recent version "not uploaded yet" that is optimized for high performance, according to my busy schedule, I could upload the new version with in three weeks, but I think the following code may help you for time beings, you may need also to enhance this portion of the code by using parameterized query and removing un-wanted fields.
 
//Insert Single Node Directly To Tree -->
public override int AddAndUpdateNode(MpttTreeNode node)
{
if (!InternalBeginUpdate())
return -1;
 
if (node.ID > 0)
{//Updateing existing one, don't update left, right or parent fields
SqlCommand cmd = new SqlCommand(string.Format(@"
UPDATE
{0} SET Title=@Title, unitID=@unitID ,divID=@divID, LeafID=@LeafID, defaultContent=@defaultContent
WHERE
ID=@ID", ContentTableName), con);
cmd.Parameters.AddWithValue("@Title", node.Title);
cmd.Parameters.AddWithValue("@unitID", node.UnitID);
cmd.Parameters.AddWithValue("@divID", node.DivID);
cmd.Parameters.AddWithValue("@defaultContent", node.ContentType);
cmd.Parameters.AddWithValue("@LeafID", node.LeafID);
cmd.Parameters.AddWithValue("@ID", node.ID);
ExecuteNonQuery(cmd);
}
else
{//Adding new one
// Get parent of the new node
MpttTreeNode parent = GetNodeByID(node.ParentID);
int nextleafID = GetNextLeafID();
 
//Output, provides you access to inserted and deleted logical tables.
// ....... BUT ............
//there is a limitation, where you have no choice than to use a workaround of "scope_identity"
//The limitation is: you cannot using OUTPUT without INTO for table which has triggers
//DML "Data Manipulation Language", puts this restriction
//SET NOCOUNT ON insert into MyTable values(...) select scope_identity() as id
//........................
 
//Output, provides you access to inserted and deleted logical tables.
SqlCommand cmd = new SqlCommand(string.Format(@"
DECLARE @MyTableVar table( ID int);
INSERT INTO
{0} (parentID,Title,lft,rgt,leafID,isLeaf,unitID,divID,defaultContent)
OUTPUT INSERTED.ID INTO @MyTableVar
VALUES
(@ParentID,@Title,@Left,@Right,@LeafID,@IsLeaf,@UnitID,@DivID,@defaultContent);
SELECT ID FROM @MyTableVar;", ContentTableName), con);
//cmd.CommandType = CommandType.TableDirect;
cmd.Parameters.AddWithValue("@Title", node.Title);
cmd.Parameters.AddWithValue("@ParentID", node.ParentID);
node.Left = (parent==null ? 1:parent.Right); // node left equals parent node right
cmd.Parameters.AddWithValue("@Left", node.Left);
node.Right = node.Left + 1; // node right equals node left + 1
cmd.Parameters.AddWithValue("@Right", node.Right);
cmd.Parameters.AddWithValue("@LeafID", nextleafID);
cmd.Parameters.AddWithValue("@IsLeaf", node.IsLeaf);
cmd.Parameters.AddWithValue("@UnitID", node.UnitID);
cmd.Parameters.AddWithValue("@DivID", node.DivID);
cmd.Parameters.AddWithValue("@defaultContent", node.ContentType);
//Object obj = ExecuteScalar(cmd);
node.ID = (int)ExecuteScalar(cmd);
node.LeafID = nextleafID;
 
//Update Nodes With Rights greater than my node right
cmd = new SqlCommand(string.Format(@"update {0} set lft=lft+2 , rgt=rgt+2 WHERE lft>{1} AND rgt>{1}", ContentTableName, node.Right), con);
ExecuteNonQuery(cmd);
 
//Update Bread Crump Nodes
cmd = new SqlCommand(string.Format(@"update {0} set rgt=rgt+2 WHERE (lft<{1} AND rgt>={2}) or (id={3})", ContentTableName, node.Left, node.Right, node.ParentID), con);
ExecuteNonQuery(cmd);
 
}
InternalEndUpdate();
//return the ID of the inserted record
return node.ID;//
}

//Delete Node With its Children
public override void DeleteNode(int id)
{
if (!InternalBeginUpdate())
return;
SqlCommand cmd = new SqlCommand(string.Format("SELECT * FROM {0} WHERE ID={1}", ContentTableName, id), con);
List<MpttTreeNode> nodes = GetNodeCollectionFromReader(ExecuteReader(cmd));
if (nodes.Count > 0)
{
MpttTreeNode node = nodes[0];
int ChildernCount = (node.Right - node.Left -1)/2;
int Difference = 2*(1+ChildernCount);
// Delete Node and its Children
cmd.CommandText = string.Format("DELETE FROM {0} WHERE (ID={1}) or (ParentID = {1})", ContentTableName, id);
ExecuteNonQuery(cmd);
// update Nodes after this deleted node
cmd.CommandText = string.Format("UPDATE {0} SET lft=lft-{1}, rgt=rgt-{1} WHERE lft>{2} AND rgt>{2}", ContentTableName,Difference, node.Right);
ExecuteNonQuery(cmd);
// update breadcrump nodes
cmd.CommandText = string.Format("UPDATE {0} SET rgt=rgt-{1} WHERE (lft<{2} AND rgt>={3}) or (id={4})", ContentTableName, Difference, node.Left, node.Right, node.ParentID);
ExecuteNonQuery(cmd);
}
InternalEndUpdate();
}
 

public override List<MpttTreeNode> GetSiblings(int nodeID)
{
if (!InternalBeginUpdate())
return null;
SqlCommand cmd = new SqlCommand(string.Format("SELECT * FROM {0} WHERE ID={1}", ContentTableName, nodeID), con);
List<MpttTreeNode> nodes = GetNodeCollectionFromReader(ExecuteReader(cmd));
List<MpttTreeNode> list = null;
if (nodes.Count > 0)
{
MpttTreeNode node = nodes[0];
cmd.CommandText = string.Format("SELECT * FROM {0} WHERE parentID={1} and NOT ID={2}", ContentTableName, node.ParentID, node.ID);
list = GetNodeCollectionFromReader(ExecuteReader(cmd));
}
else
list = null;
InternalEndUpdate();
return list;
}
// Get BreadCrump nods // Parent nodes // Parent Nodes Path //
public override List<MpttTreeNode> GetBreadCrump(int nodeID)
{
if (!InternalBeginUpdate())
return null;
MpttTreeNode node = GetNodeByID(nodeID);
SqlCommand cmd = new SqlCommand(string.Format("SELECT * FROM {0} WHERE lft<{1} and rgt>{2} Order By ParentID", ContentTableName, node.Left,node.Right), con);
List<MpttTreeNode> nodes = GetNodeCollectionFromReader(ExecuteReader(cmd));
InternalEndUpdate();
 
return nodes;
}
//-----------------------------------
// Move node with children to other parent // drag drop tree nodes
public override void MoveNode(MpttTreeNode child, MpttTreeNode newParent)//int nodeID , int newParentID)
{
//Case of: child is the root of the tree
if (child.ParentID == 0)
return;
//Get the root in order for future rebuild after updating
List<MpttTreeNode> parentBreadCrumbs = GetBreadCrump(newParent.ID);
if (parentBreadCrumbs.Count <= 0)
return;
MpttTreeNode rootNode = parentBreadCrumbs[0];
//Check conflict
parentBreadCrumbs.Reverse();//make parents before childs
foreach (MpttTreeNode bcNode in parentBreadCrumbs)
{
//Case of conflict: if moved node go to be a child of one of its children
if (bcNode.ID == child.ID)
return;
}
//Update and rebuild all
if (!InternalBeginUpdate())
return;
//
SqlCommand cmd = new SqlCommand(string.Format("UPDATE {0} SET ParentID={1} WHERE ID={2}",
ContentTableName, newParent.ID,child.ID), con);
ExecuteNonQuery(cmd);
RebuildTree(rootNode.ID, 1);
//
InternalEndUpdate();
}
 
public MpttTreeNode GetRootNode(int nodeID)
{
if (!InternalBeginUpdate())
return null;
MpttTreeNode rootNode = null;
MpttTreeNode Mynode = GetNodeByID(nodeID);
if (Mynode.ParentID == 0)
return Mynode;
List<MpttTreeNode> nodes = GetBreadCrump(nodeID);
nodes.Reverse();
foreach (MpttTreeNode node in nodes)
{
if (node.ParentID == 0)
{
rootNode = GetNodeByID(node.ID);
break;
}
}
return rootNode;
}
GeneralI could not find a solution filememberasanka_nilath30 Apr '09 - 3:08 
Hi,
 
I could not find a solution file for tree view project.
Does this work on visual studio 2005?
 
thanks
Asanka
GeneralRe: I could not find a solution filememberWael Alghool30 Apr '09 - 6:39 
It is created on VS2008, but the entire classes could be easily reused and separated, you could create a new solution on 2005 and import .cs files.
Generali'm searching for a solution with HierarchyIDmemberhapalu7 Apr '09 - 6:12 
hi,
 
your e.g. is great. but i'm searching for a solution with hierarchyid. i would write SP's in sql server 2008 and use them in my allplication code (c#). would you give me an e.g.
 
thx
 
majid rezaei
GeneralRe: i'm searching for a solution with HierarchyIDmemberWael Alghool10 Apr '09 - 4:58 
I don't understand what do you ask me to provide?, could you please clear more the meaning of your statement "give me an e.g".
 
What is HierarchyID? Is it another approach defer than MPTT?
GeneralRe: i'm searching for a solution with HierarchyIDmemberhapalu15 Apr '09 - 22:06 
HierarchID is a new data type of SQL SERVER 2008 for storing of hierarchical data. --> http://technet.microsoft.com/en-us/library/bb677290.aspx. I thought and hoped, that you know this data type. I'm searching for a solution to generate a tree by using of hierarchyid approach.
GeneralIts Great Sir.membervicky95 Feb '09 - 20:42 
Hello Sir.I'm Vijay. I read ur Source code abt modified preorder tree traversal.
Its GREAT.
But I'm facing some problems in that "MpttCoreEngine.cs" form.
 
Would u plz send me some extra reading material for that code so that I can easiely understand that form???
 
I would be most greatful if You would do the same.
 
My email Id is "vijay_bhavsar15@yahoo.co.in"
 
good day Sir.
GeneralRe: Its Great Sir.memberWael Alghool7 Feb '09 - 22:41 
Dear Vijay,
 
For more information about MPTT technique, Please, read this simple article for "Gijs Van Tulder".
 
Storing Hierarchical Data in a Database[^]
 
However, I'll try to explain MPTT more in the future.
 
Regards
Wael AlGhool
Generalgood jobmemberEka Abdurachman17 Jan '09 - 21:52 
Hierarchical tree...!
i like this code...!
Thank man...! Big Grin | :-D
GeneralExcellentmemberDr.Luiji11 Dec '08 - 11:46 
Well I've just finished to read the DrawTree. I was interested in drawing the image. I have to say is very well written, clear and full of useful comments. You put a lot of work into this. It was really a pleasure to read that function.
 
Dr.Luiji
 
Trust and you'll be trusted.
 
Try iPhone UI [^] a new fresh face for your Windows Mobile, here on Code Project.

GeneralRe: ExcellentmemberWael Alghool11 Dec '08 - 22:43 
Hope it is useful.
What do you think if I go to explain that algorithm in a separate article? is it worth to do that?
GeneralRe: ExcellentmemberDr.Luiji12 Dec '08 - 1:19 
Wael Alghool wrote:
What do you think if I go to explain that algorithm in a separate article?

The MpttCoreEngine is a good reading material, with a lot graphics, lambada and math.
I like it. At least you can add the save and load.
Wael Alghool wrote:
is it worth to do that?

Depends, It's a Bitmap of a TreeNode. Probably a section about that here, can be more helpful and it add more power to the article .
 
Kind regards
 
Dr.Luiji
 
Trust and you'll be trusted.
 
Try iPhone UI [^] a new fresh face for your Windows Mobile, here on Code Project.

GeneralRe: Excellentmembervicky95 Feb '09 - 20:40 
Hello Sir.I'm Vijay. I read ur Source code abt modified preorder tree traversal.
Its GREAT.
But I'm facing some problems in that "MpttCoreEngine.cs" form.
 
Would u plz send me some extra reading material for that code so that I can easiely understand that form???
 
I would be most greatful if You would do the same.
 
My email Id is "vijay_bhavsar15@yahoo.co.in"
 
good day Sir.

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

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130516.1 | Last Updated 10 Dec 2008
Article Copyright 2008 by Wael Alghool
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid