不要嘗試一次性載入所有層級資料。TreeView 的正確做法是懶惰載入:使用者展開節點時,才向資料庫查詢該節點的直接子節點。
CREATE TABLE TreeNode (
Id INT PRIMARY KEY,
ParentId INT NULL,
Name NVARCHAR(100),
HasChildren BIT NOT NULL DEFAULT 0 -- 關鍵:標記是否有子節點
);
// GET /api/nodes?parentId=123
public IActionResult GetChildren(int? parentId) {
var nodes = _context.TreeNodes
.Where(n => n.ParentId == parentId)
.Select(n => new {
n.Id,
n.Name,
n.HasChildren, // 前端據此決定是否顯示展開箭頭
children = new object[0] // 懶惰載入時不回傳 children
})
.ToList();
return Json(nodes);
}
// jQuery 範例
$('#tree').tree({
url: '/api/nodes',
method: 'get',
animate: true,
lines: true,
onBeforeExpand: function(node) {
if (!node.state || !node.state.loaded) {
$('#tree').tree('options').url = '/api/nodes?parentId=' + node.id;
}
},
onLoadSuccess: function(node, data) {
if (node) node.state.loaded = true;
}
});
使用 ToLookup 建立父子映射,遞迴構建樹:
var allNodes = db.TreeNodes.ToList();
var lookup = allNodes.ToLookup(n => n.ParentId ?? 0);
Func> buildTree = null;
buildTree = parentId => lookup[parentId]
.Select(n => new NodeDto {
Id = n.Id,
Name = n.Name,
Children = buildTree(n.Id)
})
.ToList();
var rootNodes = buildTree(0);
| 方案 | 適用場景 | 記憶體 | 首屏速度 |
|---|---|---|---|
| 懶惰載入 | 大數據、深層級、生產環境 | 低 | 極快 |
| 一次性載入 + ToLookup | <1000 節點、原型開發 | 中 | 快 |
| 遞迴 CTE + GroupBy | 報表匯出、離線分析 | 高 | 慢 |
TreeView 載入策略選擇:生產環境必用懶惰載入。LINQ 在此場景下的角色是構建單層查詢,而非建構整棵樹。前端框架都支援按需載入,配合 HasChildren 欄位即可完美呈現展開圖示。