十年河東,十年河西,莫欺少年窮。
學無止境,精益求精
難得有清閑的一上午,索性寫篇博客。
首先,我們需要准備一張表,如下范例:
create table TreeTable ( TreeId varchar(100) not null primary key, NodeName nvarchar(50),--名稱 FatherTreeId varchar(100),--父親Id )
為了節約時間,我直接沿用項目中的數據,作如下批量插入:
insert into TreeTable(TreeId,NodeName,FatherTreeId) select [SpClassifyId],[ClassifyName],[ParentClassifyId] from [dbo].[SM_SupplierClassify] where [ForefatherClassifyName] in('工程類','精裝修類','設計類','營銷類')
插入的數據,如下所示:

從上圖,我們可以看出,這些數據中有四種大類,分別為:('工程類','精裝修類','設計類','營銷類'),層級關系通過FatherTreeId構造,當然,此表中的層次不僅僅只有兩級,理論上支持N級,類似這種結構的數據現實生活中有很多,如:家譜,人員組織架構,營銷模式(微信的吸粉,病毒式營銷),甚至傳銷模式等等
那么,我們如何將上述數據轉變為樹狀結構的數據類型呢?
大家都會想到遞歸,但如何遞歸呢?
兩種方法,
一、如何構造:
1、遞歸式查詢,從根節點入手,查詢多次(效率非常低,在此不作介紹)
2、一次性查詢出所有數據,根據FatherTreeId,遞歸式查詢泛型(效率高。只查詢一次即可)我們介紹此種方法:
首先,我們構造如下Model
/// <summary> /// 我們要構造的樹 /// </summary> public class TreeModel { public TreeModel() { children = new List<TreeModel>(); } public string title { get; set; } public string value { get; set; } public string key { get; set; } public List<TreeModel> children { get; set; } public static TreeModel MakeTree(TreeTableModel supplierClassifyModels) { TreeModel Tree = new TreeModel(); Tree.title = supplierClassifyModels.NodeName; Tree.key = supplierClassifyModels.TreeId; Tree.value = supplierClassifyModels.TreeId; if (supplierClassifyModels.children != null) { foreach (var item in supplierClassifyModels.children) { Tree.children.Add(MakeTree(item)); } } return Tree; } } /// <summary> /// 數據庫表對應的實體Model /// </summary> public class TreeTableModel { public TreeTableModel() { children = new List<TreeTableModel>(); } public string TreeId { get; set; } public string NodeName { get; set; } public string FatherTreeId { get; set; } //子數據 public List<TreeTableModel> children { get; set; } }
上述的TreeModel中的 MakeTree 方法用於遞歸構造樹!~_~ 重點
構造方法如下:
/// <summary> /// 構造樹 /// </summary> /// <returns></returns> public static BaseResponse<List<TreeModel>> BuildTree() { using (AnFuDBEntities context = new AnFuDBEntities()) { List<TreeModel> TreeList = new List<TreeModel>(); var data = context.TreeTable.ToList(); var list = new List<TreeTableModel>(); list = Mapper.DynamicMap<List<TreeTableModel>>(data); List<TreeTableModel> TreeModels = BuildTreeModel(list, string.Empty); foreach (var item in TreeModels) { var Tree = TreeModel.MakeTree(item); TreeList.Add(Tree); } var result = TreeList; return CommonBaseResponse.SetResponse<List<TreeModel>>(result, true); } } /// <summary> /// 遞歸方法,構造樹狀泛型 /// </summary> /// <param name="AllList"></param> /// <param name="ParentClassifyId"></param> /// <returns></returns> private static List<TreeTableModel> BuildTreeModel(List<TreeTableModel> AllList, string ParentClassifyId) { List<TreeTableModel> List = new List<TreeTableModel>(); if (!string.IsNullOrEmpty(ParentClassifyId)) { List = AllList.Where(A => A.FatherTreeId == ParentClassifyId).OrderBy(A => A.NodeName).ToList(); // } else { List = AllList.Where(A => string.IsNullOrEmpty(A.FatherTreeId)).ToList(); } if (List != null) { foreach (var item in List) { item.children = BuildTreeModel(AllList,item.TreeId); if (item.children.Count == 0) { item.children = null; } } } return List.ToList(); }
通過上述代碼,我們構造了如下數據,工程類(擁有8個子類)->工程類子類中的安裝類(擁有11個子類)->......等等,多層次關系!

截止到這兒,樹的構造就完成了!
二、如何查詢:
如果直接查詢數據表,SQL支持的遞歸查詢我們可以用With....As..關鍵字來進行查詢,具體查詢語法請參考我的博客:SqlServer共用表達式(CTE)With As 處理遞歸查詢
在此,僅做簡單范例,如下:
1、查詢子分類,要求帶出該子分類的所有上級元素
WITH CTE_Tree(TreeId,NodeName,FatherTreeId ) AS (SELECT TreeId,NodeName,FatherTreeId FROM dbo.TreeTable WHERE NodeName = '電梯工程' UNION ALL SELECT o.TreeId,o.NodeName,o.FatherTreeId FROM dbo.TreeTable o INNER JOIN CTE_Tree oo ON o.TreeId=oo.FatherTreeId) SELECT distinct * FROM CTE_Tree
查詢結果如下:

說明:查詢NodeName為電梯工程的分類,帶出了電梯工程的所有上級關系!
2、查詢上級元素,要求帶出所有下級元素
WITH CTE_Tree(TreeId,NodeName,FatherTreeId ) AS (SELECT TreeId,NodeName,FatherTreeId FROM dbo.TreeTable WHERE NodeName = '安裝類' UNION ALL SELECT o.TreeId,o.NodeName,o.FatherTreeId FROM dbo.TreeTable o INNER JOIN CTE_Tree oo ON o.FatherTreeId=oo.TreeId) SELECT distinct * FROM CTE_Tree order by NodeName
查詢結果如下:

安裝類及11個子類全部查詢出來!
如果我們不使用SQL的... with.... as.....語句,要查詢出父節點及該節點下的所有子節點,該怎么查詢呢?
通過遞歸List<T>查詢方法如下:
/// <summary> /// 根據父節點ID 查詢該節點及該節點下的所有子節點 /// </summary> /// <param name="FatherNodeId"></param> /// <returns></returns> public static List<TreeTableModel> QueryTreeByFatherId(string FatherNodeId) { using (AnFuDBEntities context = new AnFuDBEntities()) { List<TreeModel> TreeList = new List<TreeModel>(); var data = context.TreeTable.ToList(); var list = new List<TreeTableModel>(); list = Mapper.DynamicMap<List<TreeTableModel>>(data); List<TreeTableModel> treeNodes = new List<TreeTableModel>(); //安裝類 對應的Id : 650FEF5E-21E6-404A-B8F7-12E82548D432 new QueryTreeModel().GetTreeNodes(list, FatherNodeId, ref treeNodes); var ef = list.Where(A => A.TreeId == FatherNodeId).FirstOrDefault(); treeNodes.Add(ef);//為了和數據庫 as with 遞歸查詢一致,將此節點加入返回值中 return treeNodes; } } /// <summary> /// 根據父類節點,查詢出所有子節點 /// </summary> /// <param name="list">數據源,非樹狀結構</param> /// <param name="TreeId">父節點Id</param> /// <param name="treeNodes">得到的子節點</param> public void GetTreeNodes(List<TreeTableModel> list, string TreeId, ref List<TreeTableModel> treeNodes) { if (list == null) return; List<TreeTableModel> sublist; if (!string.IsNullOrWhiteSpace(TreeId)) { sublist = list.Where(t => t.FatherTreeId == TreeId).ToList(); } else { sublist = list.Where(t => string.IsNullOrWhiteSpace(t.FatherTreeId)).ToList(); } if (!sublist.Any()) return; foreach (var item in sublist) { treeNodes.Add(new TreeTableModel() { TreeId=item.TreeId, FatherTreeId=item.FatherTreeId, NodeName=item.NodeName}); GetTreeNodes(list, item.TreeId, ref treeNodes); } }
執行上述待代碼:
//安裝類 對應的Id : 650FEF5E-21E6-404A-B8F7-12E82548D432 AnfuUser.QueryTreeByFatherId("650FEF5E-21E6-404A-B8F7-12E82548D432");
結果如下:

根據截圖可知,我們查詢到12條記錄!
那么,我們執行SQL如下,看看結果是否一致,如下:

根據上下兩個截圖,我們得知,查詢結果一致!
那么,如果我們要根據子節點,查詢該節點及所有上級節點,怎么通過程序查詢呢?
代碼如下:
/// <summary> /// 根據子ID 獲取自身節點及所有父節點 /// </summary> /// <param name="FatherNodeId"></param> /// <returns></returns> public static List<TreeTableModel> QueryTreeByChildId(string FatherNodeId) { using (AnFuDBEntities context = new AnFuDBEntities()) { List<TreeModel> TreeList = new List<TreeModel>(); var data = context.TreeTable.ToList(); var list = new List<TreeTableModel>(); list = Mapper.DynamicMap<List<TreeTableModel>>(data); List<TreeTableModel> treeNodes = new List<TreeTableModel>(); //安裝類 對應的Id : 650FEF5E-21E6-404A-B8F7-12E82548D432 new QueryTreeModel().GetTreeNodesByChildId(list, FatherNodeId, ref treeNodes); var ef = list.Where(A => A.TreeId == FatherNodeId).FirstOrDefault(); treeNodes.Add(ef);//為了和數據庫 as with 遞歸查詢一致,將此節點加入返回值中 return treeNodes; } } /// <summary> /// 根據子節點,查詢出所有父節點 /// </summary> /// <param name="list">數據源,非樹狀結構</param> /// <param name="TreeId">子節點Id</param> /// <param name="treeNodes">得到的子節點</param> public void GetTreeNodesByChildId(List<TreeTableModel> list, string TreeId, ref List<TreeTableModel> treeNodes) { if (list == null) return; var ef = list.Where(A => A.TreeId == TreeId).FirstOrDefault(); if (ef == null) return; List<TreeTableModel> sublist; if (!string.IsNullOrWhiteSpace(TreeId)) { var FatherTreeId = ef.FatherTreeId; sublist = list.Where(t => t.TreeId == FatherTreeId).ToList(); } else { sublist = list.Where(t => !string.IsNullOrWhiteSpace(t.FatherTreeId)).ToList(); } if (!sublist.Any()) return; foreach (var item in sublist) { treeNodes.Add(new TreeTableModel() { TreeId = item.TreeId, FatherTreeId = item.FatherTreeId, NodeName = item.NodeName }); GetTreeNodesByChildId(list, item.TreeId, ref treeNodes); } }
執行上述代碼,我們查詢電梯工程及它的所有上級節點,
AnfuUser.QueryTreeByChildId("75269B3A-0086-40A9-BE18-62820E662B69");
結果如下:

我們再通過SQL查詢如下:
WITH CTE_Tree(TreeId,NodeName,FatherTreeId ) AS (SELECT TreeId,NodeName,FatherTreeId FROM dbo.TreeTable WHERE TreeId = '75269B3A-0086-40A9-BE18-62820E662B69' UNION ALL SELECT o.TreeId,o.NodeName,o.FatherTreeId FROM dbo.TreeTable o INNER JOIN CTE_Tree oo ON o.TreeId=oo.FatherTreeId) SELECT distinct * FROM CTE_Tree
結果如下:

比對二者查詢結果是否一致呢?
@陳卧龍的博客
