[.NET]將一個DataTable資料轉成Tree狀資料排列(Hierarchical)

[.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的資料往上排列了,如下圖,

image

 

所以我們也可以寫成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:從零開始的軟體開發生活

請大家繼續支持 ^_^