近期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)通過Regex查詢其子節點。在這裡因為要返回的只是父節點的下一級子節點,所以需要增加一個節點深度條件(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; }
因為使用全路徑的結構,所以刪除一個節點及其子節點變得相當簡單,只要通過該節點的全路徑,使用Regex搜尋到節點本身及其子節點就行了。在例子中使用以下語句就輕鬆完成了刪除操作:
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