MongoDB学习之树结构例子(使用NORM驱动)

近期NoSql数据库比较火,于是本着与时俱进的态度,开始对MongoDB进行学习。学习的最好方法就是动手做做实例,于是选择了经常使用到的树结构作为入门例子。本例子将根据《Tree in MongoDB》推荐的全路径方法构建树结构。

首先要做的是如图一所示的操作页面。

mongo-tree

图一 操作页面

操作页面很简单,就是用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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值