[.NET]將一個DataTable資料轉成Tree狀資料排列(Hierarchical)
前言
最近幫同事查一個從資料庫取出組織單位的資料,然後呈現組織圖的問題。
發現該組織圖呈現在串上層的單位時,如果沒有上層資料便會發生錯誤。
解決方式可以在SQL中將資料就先排列好再傳出來(可使用SQL CTE),也可以在程式中處理。
研究
以下我們來看看在程式中要如何將DataTable的資料轉成Tree狀資料排列。
1.建立測試的資料
private DataTable GetSourceData() { DataTable result = new DataTable("OrgData"); result.Columns.Add("Node", typeof (string)); result.Columns.Add("ParentNode", typeof(string)); //加入資料 依Tree排 //result.Rows.Add("Root", string.Empty); //result.Rows.Add("Node1", "Root"); //result.Rows.Add("Node2", "Root"); //result.Rows.Add("Node3", "Root"); //result.Rows.Add("Node11", "Node1"); //result.Rows.Add("Node12", "Node1"); //result.Rows.Add("Node13", "Node1"); //result.Rows.Add("Node14", "Node1"); //result.Rows.Add("Node111", "Node11"); //result.Rows.Add("Node112", "Node11"); //result.Rows.Add("Node113", "Node11"); //result.Rows.Add("Node21", "Node2"); //result.Rows.Add("Node22", "Node2"); //result.Rows.Add("Node221", "Node22"); //result.Rows.Add("Node222", "Node22"); //result.Rows.Add("Node23", "Node2"); //result.Rows.Add("Node24", "Node2"); //result.Rows.Add("Node31", "Node3"); //result.Rows.Add("Node32", "Node3"); //result.Rows.Add("Node321", "Node32"); //result.Rows.Add("Node322", "Node32"); //改成亂排列 result.Rows.Add("Node221", "Node22"); result.Rows.Add("Root", string.Empty); result.Rows.Add("Node1", "Root"); result.Rows.Add("Node2", "Root"); result.Rows.Add("Node111", "Node11"); result.Rows.Add("Node3", "Root"); result.Rows.Add("Node11", "Node1"); result.Rows.Add("Node12", "Node1"); result.Rows.Add("Node32", "Node3"); result.Rows.Add("Node321", "Node32"); result.Rows.Add("Node13", "Node1"); result.Rows.Add("Node14", "Node1"); result.Rows.Add("Node222", "Node22"); result.Rows.Add("Node23", "Node2"); result.Rows.Add("Node112", "Node11"); result.Rows.Add("Node113", "Node11"); result.Rows.Add("Node21", "Node2"); result.Rows.Add("Node22", "Node2"); result.Rows.Add("Node24", "Node2"); result.Rows.Add("Node31", "Node3"); result.Rows.Add("Node322", "Node32"); return result; }
2.透過遞迴的方式來判斷並加入上層的資料列,如下,
private string NodeColumnName = "Node"; private string ParentNodeColumnName = "ParentNode"; private string RootParentValue = String.Empty; private void button1_Click(object sender, EventArgs e) { DataTable orgData = GetSourceData(); //先Clone舊的資料結構 DataTable TreeData = orgData.Clone(); //將Source Data 排成 Tree 狀資料 foreach (DataRow dr in orgData.Rows) { AddNode(dr, ref TreeData, ref orgData); } } private void AddNode(DataRow addRow, ref DataTable targetData, ref DataTable orgData ) { //每次加入之前要先判斷是否已在targetData之中,沒有的話,才往下做 bool isExistTargetData = (from r in targetData.AsEnumerable() where r.Field<string>(NodeColumnName) .Trim() .Equals(addRow[NodeColumnName].ToString().Trim()) select r).Any(); if (!isExistTargetData) { string parentNodeId = addRow[ParentNodeColumnName].ToString().Trim(); if (parentNodeId.Equals(RootParentValue, StringComparison.CurrentCultureIgnoreCase)) { //本身就是root節點資料,所以直接加入 targetData.Rows.Add(addRow.ItemArray); } else { //要先加入ParentNode的資料,所以找出Node欄位是ParentNodeId的那筆資料加入 var parentData = (from r in orgData.AsEnumerable() where r.Field<string>(NodeColumnName) .Trim() .Equals(parentNodeId) select r).FirstOrDefault(); AddNode(parentData, ref targetData, ref orgData); //然後再加入目前這筆資料 targetData.Rows.Add(addRow.ItemArray); } } }
執行button1的click可發現TreeData的內容已經將Parent的資料往上排列了,如下圖,
所以我們也可以寫成DataTable的Helper,如下,
public class DataTableHelper { /// <summary> /// 將一個DataTable資料轉成Tree狀資料排列(Hierarchical) /// </summary> /// <param name="orgData"></param> /// <param name="nodeColumnName"></param> /// <param name="parentNodeName"></param> /// <param name="rootParentValue">Trim後的RootParent的值</param> /// <returns></returns> public static DataTable Convert2TreeStruct(DataTable orgData, string nodeColumnName, string parentNodeName, string rootParentValue) { NodeColumnName = nodeColumnName; ParentNodeColumnName = parentNodeName; RootParentValue = rootParentValue; //先Clone舊的資料結構 DataTable TreeData = orgData.Clone(); //將Source Data 排成 Tree 狀資料 foreach (DataRow dr in orgData.Rows) { AddNode(dr, ref TreeData, ref orgData); } return TreeData; } private static string NodeColumnName = "Node"; private static string ParentNodeColumnName = "ParentNode"; private static string RootParentValue = String.Empty; private static void AddNode(DataRow addRow, ref DataTable targetData, ref DataTable orgData) { //每次加入之前要先判斷是否已在targetData之中,沒有的話,才往下做 bool isExistTargetData = (from r in targetData.AsEnumerable() where r.Field<string>(NodeColumnName) .Trim() .Equals(addRow[NodeColumnName].ToString().Trim()) select r).Any(); if (!isExistTargetData) { string parentNodeId = addRow[ParentNodeColumnName].ToString().Trim(); if (parentNodeId.Equals(RootParentValue, StringComparison.CurrentCultureIgnoreCase)) { //本身就是root節點資料,所以直接加入 targetData.Rows.Add(addRow.ItemArray); } else { //要先加入ParentNode的資料,所以找出Node欄位是ParentNodeId的那筆資料加入 var parentData = (from r in orgData.AsEnumerable() where r.Field<string>(NodeColumnName) .Trim() .Equals(parentNodeId) select r).FirstOrDefault(); AddNode(parentData, ref targetData, ref orgData); //然後再加入目前這筆資料 targetData.Rows.Add(addRow.ItemArray); } } } }
呼叫方式就改成如下,
DataTable treeData2 = DataTableHelper.Convert2TreeStruct(orgData, "Node", "ParentNode", string.Empty);
測試專案
Hi,
亂馬客Blog已移到了 「亂馬客 : Re:從零開始的軟體開發生活」
請大家繼續支持 ^_^