近期NoSql数据库比较火,于是本着与时俱进的态度,开始对MongoDB进行学习。学习的最好方法就是动手做做实例,于是选择了经常使用到的树结构作为入门例子。本例子将根据《Tree in MongoDB》推荐的全路径方法构建树结构。
首先要做的是如图一所示的操作页面。
图一 操作页面
操作页面很简单,就是用Ext的树控件创建一个树结构,通过控件上的小按钮对树进行添加、删除操作。直接单击树节点可的节点文字进行修改。因为本文的重点不是Ext界面,所以操作页面的代码就不多说,有兴趣可以下载源代码进行研究。
下面主要来学习一下操作MongoDB后台代码。首先是要根据树结构定义一个类,类定义如下:
public class Node
{
public ObjectId ID { get; set; }
public string title {get;set;}
public int depth { get; set; }
public string path { get; set; }
}
从类定义中可以看到,树节点的存储结构主要有4个项,ID为唯一编号,title为节点名称,depth是节点的深度, path是节点的全路径。
下面要完成的是子节点的查询操作,代码如下:
private string List(HttpContext context)
{
string nodeid = context.Request.Params["node"] ?? "";
JArray ja = new JArray();
using (var mongo = Mongo.Create("mongodb://192.168.0.77/Trees"))
{
var nodes = mongo.GetCollection<Node>("Node");
if (nodeid == "" | nodeid == "rootnode")
{
var q = nodes.Find(new { depth = 0 });
foreach (var c in q)
{
ja.Add(new JObject(
new JProperty("id", c.ID.ToString()),
new JProperty("text", c.title),
new JProperty("leaf", false)
));
}
}
else
{
Node node = nodes.FindOne(new { _id = new ObjectId(nodeid) });
if (node != null)
{
var q = nodes.Find(new { path = new Regex("^" + node.path), depth = node.depth + 1 });
foreach (var c in q)
{
ja.Add(new JObject(
new JProperty("id", c.ID.ToString()),
new JProperty("text", c.title),
new JProperty("leaf", false)
));
}
}
}
}
return ja.ToString();
}
List方法的代码中,第一行是获取父节点id。JArry对象ja的作用是返回子节点数组。要注意以下这句:
using (var mongo = Mongo.Create("mongodb://192.168.0.77/Trees"))
这里建议使用using关键字,主要原因是让对象自动释放连接,不然当操作频繁的时候,因为连接没有释放,会产生“norm.mongo exception: connection timeout trying to get connection from connection pool”的错误。在调试时,很是困扰了笔者一段时间。
在有些文章会使用new MongoDB连接数据库,具体有什么不同,笔者没仔细研究。使用Create方法是根据NORM的测试例子依样画葫芦而已。这个可根据个人习惯选择。如果MongoDB的端口不同,可在IP地址后加上端口号,譬如端口号为10000,可修改代码如下:
using (var mongo = Mongo.Create("mongodb://192.168.0.77:10000/Trees"))
连接字符串中的“Trees”为要连接的数据库名称。
下面一句就是从数据库中获取数据集合。如果需要返回根节点的子节点,则直接搜索depth为0的节点就行。否则需要先通过FindOne方法获取父节点,然后使用父节点的路径(path)通过正则表达式查询其子节点。在这里因为要返回的只是父节点的下一级子节点,所以需要增加一个节点深度条件(depth = node.depth + 1)。
下面是Add方法的代码,用来增加树节点:
private string Add(HttpContext context)
{
string output = "";
string title = context.Request.Params["value"] ?? "";
if (title.Length > 0)
{
string nodeid = context.Request.Params["parentid"] ?? "";
using (var mongo = Mongo.Create("mongodb://192.168.0.77/Trees"))
{
var nodes = mongo.GetCollection<Node>("Node");
var node = new Node();
node.ID = ObjectId.NewObjectId();
node.title = title;
node.depth = 0;
node.path = node.ID + ",";
if (nodeid.Length > 0)
{
Node q = nodes.FindOne(new { _id = new ObjectId(nodeid) });
if (q != null)
{
node.depth = q.depth + 1;
node.path = q.path + node.ID + ",";
}
}
nodes.Insert(node);
output = (new JObject
{
new JProperty("success",true),
new JProperty("data",new JObject(
new JProperty("id",node.ID.ToString()),
new JProperty("text",node.title),
new JProperty("leaf",false)
))
}).ToString();
}
}
else
{
output = (new JObject
{
new JProperty("success",false),
new JProperty("data","请输入节点名称")
}).ToString();
}
return output;
}
添加子节点比较简单,只要创建一个Node对象,然后使用Inser方法保存就行了。新创建的Node对象默认是顶层节点,如果父节点存在,则修改Node对象的depth属性和path属性即可。
下面是Del方法的代码,用于删除节点:
private string Del(HttpContext context)
{
string output="";
string id = context.Request.Params["id"] ?? "";
if (id.Length > 0)
{
using (var mongo = Mongo.Create("mongodb://192.168.0.77/Trees"))
{
var nodes = mongo.GetCollection<Node>("Node");
Node q = nodes.FindOne(new { _id = new ObjectId(id) });
if (q != null)
{
nodes.Delete(new { path = new Regex("^" + q.path) });
output = (new JObject
{
new JProperty("success",true),
new JProperty("data",id)
}).ToString();
}
else
{
output = (new JObject
{
new JProperty("success",false),
new JProperty("data","要删除的节点不存在或已被删除!")
}).ToString();
}
}
}
else
{
output = (new JObject
{
new JProperty("success",false),
new JProperty("data","请选择要删除的节点!")
}).ToString();
}
return output;
}
因为使用全路径的结构,所以删除一个节点及其子节点变得相当简单,只要通过该节点的全路径,使用正则表达式搜索到节点本身及其子节点就行了。在例子中使用以下语句就轻松完成了删除操作:
nodes.Delete(new { path = new Regex("^" + q.path) });
下面是Edit方法的代码,用于修改节点的名称:
private string Edit(HttpContext context)
{
string output = "";
string id = context.Request.Params["id"] ?? "";
string title = context.Request.Params["value"] ?? "";
if (title.Length > 0)
{
if (id.Length > 0)
{
using (var mongo = Mongo.Create("mongodb://192.168.0.77/Trees"))
{
var nodes = mongo.GetCollection<Node>("Node");
Node q = nodes.FindOne(new { _id = new ObjectId(id) });
if (q != null)
{
q.title = title;
nodes.Save(q);
output = (new JObject
{
new JProperty("success",true),
new JProperty("data",id)
}).ToString();
}
else
{
output = (new JObject
{
new JProperty("success",false),
new JProperty("data","要修改的节点不存在或已被删除!")
}).ToString();
}
}
}
else
{
output = (new JObject
{
new JProperty("success",false),
new JProperty("data","要修改的节点不存在或已被删除!")
}).ToString();
}
}
else
{
output = (new JObject
{
new JProperty("success",false),
new JProperty("data","请输入节点名称!")
}).ToString();
}
return output;
}
代码中首先要判断提交过来的节点名称是否为空,如果不为空则继续判断提交的节点id是否正确,接着需要通过 FindOne方法搜索节点,修改title属性后,通过save方法保存即可完成操作。
例子已经完成了,从例子中可以看到,使用NORM操作MongoDB相当方便,代码很简洁。
通过例子可以看到,使用全路径的方法,在MongoDB中创建一个无级树是相当的方便,因为路径不受数据库字段长度的限制,不过如果考虑MongoDB的存储大小的话,估计也是一个问题,不过这个有待测试。这也是NoSql数据库的一个优势吧。
源代码下载地址:http://download.csdn.net/source/2652662