在开发一个.NET桌面应用程序的内容映射模块时,需要将内容分类成类似层次树的结构,并且需要以多种格式表示这棵树,比如文本、表格、节点和数据库格式。在网上搜索了能够将层次树表示到SQL2005数据库的.NET库,但没能找到。不过,找到了一篇名为"Storing Hierarchical Data in a Database"的文章,作者是Gijs Van Tulder,它解释了MPTT(Modified Preorder Tree Traversal)的概念。不幸的是,这篇文章是为PHP开发者写的,所以将这个概念翻译成了C#和SQL2005代码。此外,还编写了一些算法来将树表示为文本、表格、树视图节点和图形格式。还使用了提供者模型技术作为支持各种数据库(如SQLite)的一步。
最终,将所有这些内容整合到一个项目中,但整个类可以很容易地重用并分离成类库,即使是写的创新算法也可以单独重用,比如用于渲染树的图形表示的算法。现在,认为已经找到了一种有效的方法来添加、插入、删除和检索树元素,包括使用SQL2005数据库的有效模式,即MPTT,它通过每次活动只需一个查询来最小化数据库查询的数量。
什么是层次树?简单来说,它是一种看起来像真实树的数据结构,尽管层次树通常与真实树相反(即根在顶部,叶子在底部),该结构的元素通过父子关系相互关联,这些关系通过连接线表示,称为“分支”,此外,没有上级的元素称为“根”,没有子元素的元素称为“叶子”。
以下示例展示了动物王国树的文本表示:
Animal Kingdom
#Backbones
##Mammal
##Lungs
##Reptile
##Bird
##Gills
###Fish
###Amphibian
#No Backbones
##Starfish
##Mollusk
###Snail
###Clam
##Jointed Legs
###Insect
###Spider
###Crustacean
因此,有1个根(Animal Kingdom)和12个叶子(Mammal, Lungs, Reptile, Bird, Fish, Amphibian, Starfish, Snail, Clam, Insect, Spider, Crustacean),有18行,每行包含一个树元素/节点,节点标题前面的(#)数量表示元素在树中的深度级别,具有相同级别和相同父级的元素称为“兄弟”。
这种树的图形表示存在于图2中,其中矩形是节点的象征性表示,此外,这种树的表格表示存在于图3中。
使用演示项目:回到上一节,复制图1的文本,并将其粘贴到演示项目界面右侧的TextBox中,然后,按下标题为"<"的按钮,将文本转换为左侧TreeView控件中的传统树表示。
通过点击名为"Tabular"的单选按钮显示树的表格表示,并使用名为"Textual"的另一个单选按钮在文本和表格之间切换。
要将刚刚转换的树保存到SQL2005数据库,应该首先选择一个现有的数据库,但不必创建任何表,只需通过点击名为"Connection String"的按钮构建一个有效的连接字符串,构建连接字符串后,只需点击名为"Save"的按钮即可将树保存到名为"tblTree"的表中,如果该表不存在,则会自动创建;如果已存在,则会被截断。
当然,可以随时从相同的连接字符串重新加载这棵树,只要正确提供了它,只需编写连接字符串并点击"Load"按钮。但是,不必每次打开演示项目时都输入连接字符串,它会将连接字符串自动保存到名为constring.txt的文件中。
现在,可以通过点击"Draw"按钮来享受树的图形表示。
使用代码:只需要创建一个MpttCoreEngine类的实例并调用其接口中的任何方法。
MpttCoreEngine engine = new MpttCoreEngine();
这个类包含了所有编写的算法,用于以各种格式表示层次树,以下列表包含了这个类中最重要的方法。
SetConnectionString:使用此函数为引擎提供连接字符串 ConvertMpttIntoTree:使用此函数从数据库加载树节点 ConvertTreeIntoMptt:使用此函数将树节点保存到数据库 ConvertTextIntoTree:将文本树转换为适合TreeView的TreeNode ConvertTextTableIntoTree:将表格树转换为适合TreeView的TreeNode ConvertTreeIntoText:将TreeNode转换回文本表示 ConvertTreeIntoTable:将TreeNode转换回表格表示 DrawTree:在Image上绘制TreeNode并返回Image以供后续使用
以下代码是从演示项目中截取的,展示了如何使用这些方法。
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");
}
}
代码充满了有趣的点,其中大多数可能需要单独的文章来解释。这里是最重要的一些:
绘制树节点的算法非常复杂,使用了匿名函数、递归和回调来计算节点的准确位置。可以在DrawTree函数中找到该算法。
MPTT概念在互联网上的许多地方都有解释,不过,将这个概念与一个称为"提供者模型"的设计模式结合起来,创建了一个名为TreeProvider的抽象类,用于实现并支持任何数据库类型,如SQL2005、MySQL或SQLite。