使用 C# 3.0 和 SQL 2005 通过修改后的先序遍历技术表示的层次树






4.93/5 (26投票s)
将各种算法收集到一个库中,用于在不同格式之间转换层次树,并允许它们表示为 SQL 2005,支持的格式有 TreeView、文本、表格、修改后的先序遍历树和图形。
引言
在我从事 (CMS) 内容管理系统的工作中,并且在内容映射模块(一个 .NET 桌面应用程序)中,我需要将我的内容分类到一系列的类别中,例如层次树,但我也需要将该树表示成多种格式,例如文本、表格、节点和数据库。
我搜索了网络,寻找哪个 .NET 库可以将我的树表示到 SQL2005 数据库中。我找不到那个库,但我找到了一篇(Gijs Van Tulder)题为 Storing Hierarchical Data in a Database 的文章,其中解释了 MPTT (修改后的先序遍历树) 的概念。不幸的是,它是为 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 个根(动物王国
)和 12 个叶子(哺乳动物
、肺
、爬行动物
、鸟类
、鱼类
、两栖动物
、海星
、蜗牛
、蛤蜊
、昆虫
、蜘蛛
、甲壳类动物
),我们有 18 行,每行包含一个树元素/节点,节点标题前的 (#) 数量代表元素在树中的深度级别,具有相同级别且具有相同父级的元素称为“兄弟姐妹”。
这种树的图形表示存在于图 2 中,其中矩形是节点的符号表示,并且这种树的表格表示也存在于图 3 中。
ID | 父节点 ID | 标题 | 左侧 | 右侧 |
1 | 0 | 动物王国 | 1 | 36 |
2 | 1 | 脊椎动物 | 2 | 17 |
3 | 2 | 哺乳动物 | 3 | 4 |
4 | 2 | 肺 | 5 | 6 |
5 | 2 | 爬行动物 | 7 | 8 |
6 | 2 | Bird | 9 | 10 |
7 | 2 | 鳃 | 11 | 16 |
8 | 7 | 鱼类 | 12 | 13 |
9 | 7 | 两栖动物 | 14 | 15 |
10 | 1 | 无脊椎动物 | 18 | 35 |
11 | 10 | 海星 | 19 | 20 |
12 | 10 | 软体动物 | 21 | 26 |
13 | 12 | 蜗牛 | 22 | 23 |
14 | 12 | 蛤蜊 | 24 | 25 |
15 | 10 | 有关节腿 | 27 | 34 |
16 | 15 | 昆虫 | 28 | 29 |
17 | 15 | 蜘蛛 | 30 | 31 |
18 | 15 | 甲壳类动物 | 32 | 33 |
使用演示项目
回到上一节,复制图 1 的文本,并将其粘贴到演示项目界面右手边的TextBox
中,然后,按标题为“<<
”的按钮,将文本转换为左手边TreeView
控件中的传统树表示。
通过单击名为“表格”的单选按钮来显示树的表格表示,并使用名为“文本”的另一个单选按钮在文本和表格之间切换。
要将您刚刚转换的树保存到 SQL2005 数据库,您应该首先选择一个现有数据库,但您不必创建任何表,只需通过单击名为“连接字符串”的按钮来构建一个有效的连接字符串,在组合连接字符串后,只需单击名为“保存”的按钮即可将树保存到名为“tblTree
”的表中,如果该表不存在,它将自动创建,如果存在,它将被截断。
当然,如果您提供了正确的连接字符串,您可以随时再次加载此树,只需组合连接字符串并单击“加载”按钮即可。但是,您不必每次打开演示项目时都输入连接字符串,它会自动将连接字符串保存到名为 constring.txt 的文件中。
现在,您可以通过单击“绘制”按钮来享受树的图形表示。
Using the Code
您只需创建一个 MpttCoreEngine
类的对象,然后调用其接口中的任何方法。
MpttCoreEngine engine = new MpttCoreEngine();
此类包含编写用于以各种格式表示层次树的所有算法,以下列表包含此类中最重要的方法。
SetConnectionString | 使用此函数向引擎提供连接字符串 |
ConvertMpttIntoTree | 使用此函数从数据库加载树节点 |
ConvertTreeIntoMptt | 使用此函数将树节点保存到数据库 |
ConvertTextIntoTree | 将文本树转换为适合TreeView 的TreeNode |
ConvertTextTableIntoTree | 将表格树转换为适合TreeView 的TreeNode |
ConvertTreeIntoText | 将TreeNode 转换回文本表示 |
ConvertTreeIntoTable | 将TreeNode 转换回表格表示 |
DrawTree | 将TreeNode 绘制到Image 上并返回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");
}
}
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();
}
关注点
代码充满了有趣的要点,其中许多可能需要单独的文章来解释。以下是最重要的要点
- 绘制树节点的算法非常复杂,我使用了匿名函数、递归和回调来计算节点的确切位置。您可以在
DrawTree
函数中找到该算法。 - MPTT 的概念在网络上的几个地方都有解释,但我将 MPTT 的概念与一个称为“提供者模型”的设计模式结合起来,其中创建了一个名为
TreeProvider
的抽象
类来实现和支持任何数据库类型,如 SQL2005、MySQL 或 SQLite。 - 您可以通过仅复制名为 Core 的文件夹到任何您想要的项目中,来轻松地将演示项目的核心分离成一个类库。
历史
版本 1.0
TreeView
表示文本
表示- 表格表示
- MPTT 表示
- 图形表示
- SQL2005 支持