更有效地构建树?

时间:2010-11-01 13:17:44

标签: c# linq entity-framework

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

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

alt text

以下是相关代码:

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);
    }
}

8 个答案:

答案 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)

用以下内容包装TreeView修改:

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

获得更好的表现。

jgauffin指出here

答案 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的答案。