VB.NET从ODBC dBase数据表快速构建多级TreeView的优化求助
兄弟,7分钟加载TreeView确实太离谱了,我完全能体会你卡了一周的崩溃!核心问题其实出在反复发起数据库查询上——你的代码每找一层子节点就跑一次SQL,4500行数据可能要执行几百甚至上千次查询,数据库连接、数据传输的开销加起来直接把时间拖垮了。下面给你几个针对性的优化方案,改完绝对能把时间压缩到几秒内:
核心优化思路:一次性加载所有数据,内存中构建树结构
最关键的是把所有需要的节点数据一次性从数据库拉取到内存,然后用内存中的数据递归/迭代构建TreeView,彻底避免反复访问数据库。
具体优化步骤
1. 一次性获取全量节点数据
把原来多次查询改成一次查询,取出所有符合条件的节点,只做一次数据库交互:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click TreeView1.Visible = False ' 一次性拉取所有需要的节点数据,只查一次数据库 Dim allNodes As DataTable = PDataset("SELECT CompID, ParentCompID, CompName, CompNo FROM [Component] Where CHAR_LENGTH(CompNo) = 14 AND ParentCompID IS NOT NULL").Tables(0) DataGridView5.DataSource = allNodes TreeView1.BeginUpdate() TreeView1.Nodes.Clear() ' 先筛选出根节点(ParentCompID=1040965的记录) Dim rootRows = allNodes.Select("ParentCompID = 1040965") For Each dr As DataRow In rootRows Dim tnParent As New TreeNode($"{dr("CompNo").ToString().Trim()} {dr("CompName").ToString().Trim()}") tnParent.Tag = dr("CompID").ToString() ' 把CompID存在Tag里,方便后续找子节点 tnParent.Expand() TreeView1.Nodes.Add(tnParent) ' 调用内存版的子节点填充方法 FillChildFromMemory(tnParent, dr("CompID").ToString(), allNodes) Next TreeView1.Sort() TreeView1.Visible = True TreeView1.EndUpdate() End Sub
2. 内存中递归查找子节点,不再碰数据库
修改填充子节点的方法,直接从内存的DataTable里筛选数据,彻底告别反复查询:
Public Sub FillChildFromMemory(parent As TreeNode, parentID As String, allNodes As DataTable) ' 用DataTable的Select方法在内存中快速筛选子节点 ' 注意:如果CompID是字符串类型要加单引号,数字类型则去掉 Dim childRows = allNodes.Select($"ParentCompID = '{parentID}'") For Each dr As DataRow In childRows Dim child As New TreeNode($"{dr("CompNo").ToString().Trim()} {dr("CompName").ToString().Trim()}") child.Tag = dr("CompID").ToString() child.Collapse() parent.Nodes.Add(child) ' 递归调用内存版方法 FillChildFromMemory(child, dr("CompID").ToString(), allNodes) Next End Sub
3. 进阶优化:用Dictionary构建索引,速度再翻倍
如果DataTable的Select还是有点慢,可以提前把数据按ParentCompID分组存到字典里,查找子节点直接是O(1)的时间复杂度:
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click TreeView1.Visible = False Dim allNodes As DataTable = PDataset("SELECT CompID, ParentCompID, CompName, CompNo FROM [Component] Where CHAR_LENGTH(CompNo) = 14 AND ParentCompID IS NOT NULL").Tables(0) DataGridView5.DataSource = allNodes ' 提前构建ParentCompID到子节点列表的索引字典 Dim nodeDict As New Dictionary(Of String, List(Of DataRow))() For Each dr As DataRow In allNodes.Rows Dim parentID As String = dr("ParentCompID").ToString() If Not nodeDict.ContainsKey(parentID) Then nodeDict(parentID) = New List(Of DataRow)() End If nodeDict(parentID).Add(dr) Next TreeView1.BeginUpdate() TreeView1.Nodes.Clear() ' 直接从字典里取根节点 If nodeDict.ContainsKey("1040965") Then For Each dr As DataRow In nodeDict("1040965") Dim tnParent As New TreeNode($"{dr("CompNo").ToString().Trim()} {dr("CompName").ToString().Trim()}") tnParent.Tag = dr("CompID").ToString() tnParent.Expand() TreeView1.Nodes.Add(tnParent) FillChildWithDict(tnParent, dr("CompID").ToString(), nodeDict) Next End If TreeView1.Sort() TreeView1.Visible = True TreeView1.EndUpdate() End Sub Public Sub FillChildWithDict(parent As TreeNode, parentID As String, nodeDict As Dictionary(Of String, List(Of DataRow))) ' 直接从字典里取子节点,无需筛选 If nodeDict.ContainsKey(parentID) Then For Each dr As DataRow In nodeDict(parentID) Dim child As New TreeNode($"{dr("CompNo").ToString().Trim()} {dr("CompName").ToString().Trim()}") child.Tag = dr("CompID").ToString() child.Collapse() parent.Nodes.Add(child) FillChildWithDict(child, dr("CompID").ToString(), nodeDict) Next End If End Sub
额外小优化
- 给数据库里的
ParentCompID字段加个索引,让一次性查询的速度更快; - 你已经在用
BeginUpdate/EndUpdate了,这点做得很好,能避免添加节点时反复刷新UI; - 如果
CompID是数字类型,记得去掉SQL语句里的单引号,避免不必要的类型转换开销。
按上面的方案改完,数据库只查询一次,剩下的操作全在内存里跑,构建TreeView的时间绝对能从7分钟降到几秒内,完全满足你的需求!
内容的提问来源于stack exchange,提问作者agustabe
相关产品推荐
相关产品推荐

