萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 程式設計 >  >> 網路程式設計 >> ASP.NET 程式設計

ASP.NET TreeView 動態繫結遞迴資料 (資料庫父子關係)

前言

TreeView 常用於顯示多層級選單、組織架構、分類目錄。資料庫通常採用 **遞迴關聯** (Self-referencing FK):`ParentId` 指向同表 `Id`。本文示範從資料庫讀取 `DataTable`,遞迴建立 `TreeNode`。

資料表結構

CREATE TABLE MenuTree (
    MenuId INT PRIMARY KEY,
    ParentId INT NULL REFERENCES MenuTree(MenuId), -- 0 或 NULL 為根
    MenuName NVARCHAR(100) NOT NULL,
    MenuOrder INT DEFAULT 0,
    NavigateUrl NVARCHAR(255) NULL
);

後台完整程式碼

using System;
using System.Data;
using System.Configuration;
using System.Data.SqlClient;
using System.Web.UI.WebControls;

public partial class TreeViewDemo : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack) BindTree();
    }

    private void BindTree()
    {
        DataTable dt = GetTreeData();
        // 先建立所有節點字典 (Key=MenuId),再建立父子關係,避免遞迴多次查詢
        var nodeDict = new System.Collections.Generic.Dictionary();
        
        foreach (DataRow row in dt.Rows)
        {
            var node = new TreeNode
            {
                Text = row["MenuName"].ToString(),
                Value = row["MenuId"].ToString(),
                NavigateUrl = row["NavigateUrl"]?.ToString(),
                ToolTip = row["MenuName"].ToString()
            };
            nodeDict[(int)row["MenuId"]] = node;
        }

        // 建立父子關係
        foreach (DataRow row in dt.Rows)
        {
            int id = (int)row["MenuId"];
            int parentId = row["ParentId"] == DBNull.Value ? 0 : (int)row["ParentId"];
            var node = nodeDict[id];
            
            if (parentId == 0 || !nodeDict.ContainsKey(parentId))
            {
                TreeView1.Nodes.Add(node); // 根節點
            }
            else
            {
                nodeDict[parentId].ChildNodes.Add(node); // 掛到父節點
            }
        }
        
        TreeView1.ExpandAll(); // 或 ExpandDepth(1)
    }

    private DataTable GetTreeData()
    {
        string connStr = ConfigurationManager.ConnectionStrings["TreeConn"].ConnectionString;
        const string sql = "SELECT MenuId, ParentId, MenuName, MenuOrder, NavigateUrl FROM MenuTree ORDER BY ParentId, MenuOrder";
        
        using (var conn = new SqlConnection(connStr))
        using (var da = new SqlDataAdapter(sql, conn))
        {
            var dt = new DataTable();
            da.Fill(dt);
            return dt;
        }
    }
}

前端 .aspx

<asp:TreeView ID="TreeView1" runat="server" 
    ShowLines="True" 
    NodeIndent="15"
    ExpandDepth="1">
    <NodeStyle Font-Names="微軟正黑體" Font-Size="14px" VerticalPadding="3" />
    <HoverNodeStyle ForeColor="Blue" />
    <SelectedNodeStyle BackColor="#E0E0E0" ForeColor="Black" />
</asp:TreeView>

效能優化:大量節點 (超過 5000)

  • 分頁載入 (Lazy Loading):設定 `PopulateNodesFromClient="True"`,節點展開時觸發 `TreeNodePopulate` 事件,動態查詢子節點。
  • 快取 DataTable:資料不常變動時,用 `Cache["TreeData"]` 存 10-30 分鐘。
  • 只載入可見層級:`ExpandDepth="1"` 預設只展開第一層。

Lazy Loading 範例

<asp:TreeView ID="TreeView1" runat="server" PopulateNodesFromClient="True" OnTreeNodePopulate="TreeView1_TreeNodePopulate" />

// 後台
protected void TreeView1_TreeNodePopulate(object sender, TreeNodeEventArgs e)
{
    int parentId = int.Parse(e.Node.Value);
    DataTable children = GetChildren(parentId); // 只查該父節點的直屬子節點
    foreach (DataRow row in children.Rows)
    {
        var node = new TreeNode(row["MenuName"].ToString(), row["MenuId"].ToString());
        node.PopulateOnDemand = true; // 允許繼續展開
        e.Node.ChildNodes.Add(node);
    }
}

private DataTable GetChildren(int parentId)
{
    // SELECT ... WHERE ParentId = @ParentId
}

現代替代:jsTree / zTree / Ant Design Tree (前端元件 + Web API)

新專案建議:後端提供 `/api/tree/nodes?parentId=0` 扁平化 JSON,前端用成熟 JS Tree 元件渲染、搜尋、拖曳、複選,體驗遠優於 WebForms TreeView。

copyright © 萬盛學電腦網 all rights reserved