更有效地构建树?

我想知道这段代码是否足够好,或者是否有明显的新手no-no’s。

基本上我正在填充一个TreeView,列出我数据库中的所有Departments。 这是entity framework模型:

替代文字

这是有问题的代码:

private void button1_Click(object sender, EventArgs e) { DepartmentRepository repo = new DepartmentRepository(); var parentDepartments = repo.FindAllDepartments() .Where(d => d.IDParentDepartment == null) .ToList(); foreach (var parent in parentDepartments) { TreeNode node = new TreeNode(parent.Name); treeView1.Nodes.Add(node); var children = repo.FindAllDepartments() .Where(x => x.IDParentDepartment == parent.ID) .ToList(); foreach (var child in children) { node.Nodes.Add(child.Name); } } } 

编辑:

到目前为止的好建议。 我想,使用整个系列是有道理的。 但是,如果收集量大到200,000个条目,会发生什么? 这不会破坏我的软件吗?

 DepartmentRepository repo = new DepartmentRepository(); var entries = repo.FindAllDepartments(); var parentDepartments = entries .Where(d => d.IDParentDepartment == null) .ToList(); foreach (var parent in parentDepartments) { TreeNode node = new TreeNode(parent.Name); treeView1.Nodes.Add(node); var children = entries.Where(x => x.IDParentDepartment == parent.ID) .ToList(); foreach (var child in children) { node.Nodes.Add(child.Name); } } 

这看起来不错,但想想数十万个节点的集合。 最好的方法是异步加载 – 请注意,这并不是必须同时加载所有元素。 默认情况下,您的树视图可以折叠,您可以在用户展开树的节点时加载其他级别。 让我们考虑这样的情况:你有一个包含100个节点的根节点,每个节点至少包含1000个节点。 100 * 1000 = 100000个节点要加载 – 差不多,不是吗? 要减少数据库流量,您可以先加载前100个节点,然后在用户扩展其中一个节点时,可以加载其1000个节点。 这将节省大量时间。

既然您无论如何都要获得所有部门,为什么不在一个查询中执行此操作,在该查询中获取所有部门,然后针对内存中集合而不是数据库执行查询。 那会更有效率。

从更一般的意义上讲,任何递归的数据库模型都可能导致问题,特别是如果这可能最终成为一个相当深的结构。 一个可能需要考虑的事情是每个部门都要存储它的所有祖先,这样你就可以一次性获取它们而不必一次查询所有它们。

根据您的编辑,您可能需要考虑另一种可扩展以处理非常大的树结构的数据库模式。

关于他们如何处理层次结构的关于fogbugz博客的解释。 他们还链接到Joe Celko的这篇文章以获取更多信息。

事实certificate,Joe Celko解释了这个问题非常酷的解决方案。 而不是试图在整个数据库中维护一堆父/子关系 – 这需要递归SQL查询来查找节点的所有后代 – 我们用每个案例标记“左”和“右”值来计算遍历树深度优先并随着我们的进行计算。 每当在遍历期间第一次看到节点的“左”值时,设置“左”值,并且当从树上远离节点向后行走时设置“右”值。 一张图片可能更有意义:

嵌套集SQL模型允许我们在不牺牲性能的情况下添加案例层次结构。

这有什么用? 现在我们只询问所有具有2到9之间“左”值的情况,以便在一个快速索引查询中找到B的所有后代。 通过要求“左”小于6(G自己的“左”)和“右”大于6的节点来找到G的祖先。在所有数据库中工作。 大大提高性能 – 特别是在查询大型层次结构时。

假设您从数据库获取数据,首先想到的是,您将在数据库中为数据库中的父项数量增加n + 1次。 您应该尝试在一次点击中获得整个树结构。

其次,当您似乎使用存储库模式时,您似乎可以看到想法模式,因此您可能希望查看IoC 。 它允许您将对特定对象(例如存储库)的依赖性注入到将要使用它的类中,从而可以更轻松地进行unit testing。

第三,无论您从何处获取数据,都要将数据结构化为树数据结构,转换为一个服务,该服务返回一个包含已组织的所有部门的对象(这基本上成为DTO )。 这将有助于减少代码重复 。

有任何你需要应用yagni原则。 这基本上说你应该只做一些事情,如果你需要它,所以如果你上面提供的代码是完整的,不需要进一步的工作,并且function不触及它。 选择n + 1的性能问题也是如此,如果你没有看到任何性能命中没有做任何事情,因为它可能是过早的优化 。


在你的编辑中

 DepartmentRepository repo = new DepartmentRepository(); var entries = repo.FindAllDepartments(); var parentDepartments = entries.Where(d => d.IDParentDepartment == null).ToList(); foreach (var parent in parentDepartments) { TreeNode node = new TreeNode(parent.Name); treeView1.Nodes.Add(node); var children = entries.Where(x => x.IDParentDepartment == parent.ID).ToList(); foreach (var child in children) { node.Nodes.Add(child.Name); } } 

你还有一个n + 1问题。 这是因为只有在调用ToList()或迭代枚举时才从数据库中检索数据。 这会更好。

 var entries = repo.FindAllDepartments().ToList(); var parentDepartments = entries.Where(d => d.IDParentDepartment == null); foreach (var parent in parentDepartments) { TreeNode node = new TreeNode(parent.Name); treeView1.Nodes.Add(node); var children = entries.Where(x => x.IDParentDepartment == parent.ID); foreach (var child in children) { node.Nodes.Add(child.Name); } } 

想到的事情:

它看起来像.ToList()是不必要的。 如果您只是迭代返回的结果,为什么还需要额外的步骤呢?

将此函数移动到自己的事物中并移出事件处理程序。

正如其他人所说,你可以在一次通话中获得全部结果。 按IDParentDepartment排序,以便首先使用null。 这样,您应该能够在一次调用中获取部门列表,并且只在将子部门添加到已存在的父部门之后迭代它。

用以下内容包装TreeView修改:

treeView.BeginUpdate(); //在这里修改树 treeView.EndUpdate();

为了获得更好的性能。

jgauffin 在这里指出

这应该只使用一个(尽管可能很大)调用数据库:

 Departments.Join( Departments, x => x.IDParentDepartment, x => x.Name, (o,i) => new { Child = o, Parent = i } ).GroupBy(x => x.Parent) .Map(x => { var node = new TreeNode(x.Key.Name); x.Map(y => node.Nodes.Add(y.Child.Name)); treeView1.Nodes.Add(node); } ) 

‘Map’只是IEnumerables的’ForEach’:

 public static void Map(this IEnumerable source, Action func) { foreach (T i in source) func(i); } 

注意:如果Departments表很大,这仍然无济于事,因为’Map’实现了sql语句的结果,就像’ToList()’那样。 您可能会考虑Piotr的答案。

除了Bronumski和Keith Rousseau之外,还要添加带有节点(Tag)的DepartmentID,这样您就不必重新查询数据库以获取departmentID