我想知道这段代码是否足够好,或者是否有明显的新手no-no's。
基本上我正在填充一个TreeView,列出我数据库中的所有Departments。这是实体框架模型:
以下是相关代码:
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);
}
}
答案 0 :(得分:3)
由于您无论如何都要获得所有部门,为什么不在一个查询中执行此操作,在该查询中获取所有部门,然后针对内存中集合而不是数据库执行查询。那会更有效率。
从更一般的意义上讲,任何递归的数据库模型都可能导致问题,特别是如果这可能最终成为一个相当深的结构。一个可能的考虑因素是每个部门都要存储它的所有祖先,这样你就可以一次性获取所有祖先,而不必一次查询所有祖先。
答案 1 :(得分:2)
假设您从数据库获取数据,首先要想到的是,您将在数据库中为数据库中的父数据库n+1命中数据库IoC。你应该尝试在一次点击中获得整个树形结构。
其次,当您似乎使用存储库模式时,您似乎可以看到想法模式,因此您可能希望查看DTO。它允许您将对特定对象(例如存储库)的依赖性注入到将要使用它的类中,以便更容易进行单元测试。
第三,无论您从何处获取数据,都要将数据结构转换为树数据结构,并将其返回给您一个包含所有已组织部门的对象(这基本上变为{{3 }})。这有助于您减少code duplication。
您需要应用yagni原则。这基本上说你应该只做一些事情,如果你需要它,所以如果你上面提供的代码是完整的,不需要进一步的工作,功能不要触摸它。选择n + 1的性能问题也是如此,如果你没有看到任何性能命中没有做任何事情,可能是premature optimization。
在你的编辑中
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);
}
}
答案 2 :(得分:2)
根据您的编辑,您可能需要考虑另一种可扩展以处理非常大的树结构的数据库模式。
关于fogbugz blog如何处理层次结构,有一个解释。他们还链接到Joe Celko的this文章以获取更多信息。
原来,Joe Celko解释了这个问题的一个非常酷的解决方案。而不是试图在整个数据库中维护一堆父/子关系 - 这将需要递归SQL查询来查找节点的所有后代 - 我们用每个案例标记“左”和“右”值来计算遍历树深度优先并随着我们的进行计算。每当在遍历期间第一次看到节点的“左”值时,设置“左”值,并且当从树上远离节点向后行走时设置“右”值。一张照片可能更有意义:
嵌套集SQL模型允许我们在不牺牲性能的情况下添加案例层次结构。
这有什么用?现在我们只询问所有具有2到9之间“左”值的情况,以便在一个快速索引查询中找到B的所有后代。通过要求“左”小于6(G自己的“左”)和“右”大于6的节点找到G的祖先。在所有数据库中工作。大大提高性能 - 特别是在查询大型层次结构时。
答案 3 :(得分:1)
想到的事情:
看起来.ToList()
是不必要的。如果您只是迭代返回的结果,为什么还要为额外的步骤而烦恼呢?
将此函数移动到自己的东西中并移出事件处理程序。
正如其他人所说,你可以在一次通话中获得整个结果。按IDParentDepartment
排序,以便空的第一个。这样,您应该能够在一次调用中获取部门列表,并且只有在将子部门添加到已存在的父部门时才迭代它。
答案 4 :(得分:1)
这对我来说没问题,但想想数十万个节点的集合。执行此操作的最佳方法是异步加载 - 请注意,确实不必同时加载所有元素。默认情况下,您的树视图可以折叠,您可以在用户展开树的节点时加载其他级别。让我们考虑这样的情况:你有一个包含100个节点的根节点,每个节点至少包含1000个节点。 100 * 1000 =加载100000个节点 - 差不多,不是吗?要减少数据库流量,您可以先加载前100个节点,然后在用户扩展其中一个节点时,可以加载其1000个节点。这将节省大量时间。
答案 5 :(得分:0)
答案 6 :(得分:0)
除了Bronumski和Keith Rousseau的回答 还要添加带有节点(Tag)的DepartmentID,这样您就不必重新查询数据库以获取departmentID
答案 7 :(得分:0)
这应该只使用一个(尽管可能很大)调用数据库:
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);
}
)
“地图”只是IEnumerables的“ForEach”:
public static void Map<T>(this IEnumerable<T> source, Action<T> func)
{
foreach (T i in source)
func(i);
}
注意:如果Departments表很大,这仍然无济于事,因为'Map'实现了sql语句的结果,就像'ToList()'那样。您可能会考虑Piotr的答案。