如何使用LINQ按深度级别订购对象层次结构?

时间:2015-08-04 06:48:14

标签: c# linq sorting hierarchy hierarchical-data

考虑这个层次结构

                     A
                     |
        --------------------------
        |                        |
        B                        C
        |                        |
--------|--------            ---------
|       |       |            |       |
D       E       F            G       H
|       |                    |       |
|   ---------             -------    |
|   |       |             |     |    |
I   J       K             L     M    N

每个对象都有一个Parent属性和一个choldren的Items集合,因此,例如,E的父节点为B,N为H,等等。对于父节点,A的值为null。 B.Items包含D-F等。

什么是LINQ语句,我可以根据它们的级别对它们进行排序?我并不关心一个级别内的排序顺序(即D-H的顺序并不重要,但它们必须在B和C之后,它们必须在A之后。

我能想到的只有两个独立的linq语句:

  1. 对此进行聚合,计算并存储各个级别
  2. 对Level的结果排序运行第二个LINQ查询。
  3. B当然很容易。这是我挣扎的A。我当然可以在程序上做到这一点,但我必须认为这可以简化为LINQ语句。

4 个答案:

答案 0 :(得分:7)

您没有指定查询的目标,因此有多个正确的答案:

  1. LINQ to SQL或LINQ to Entities - 不存在对递归查询的支持,因此您必须将数据加载到内存中并执行LINQ to Objects查询,或者在数据库中使用存储过程(最有可能使用Common Table Expression)。您还可以在数据库中准备VIEW并将其映射到您的EF模型。

  2. LINQ to Objects更适合这项工作,但IMO你最好用一种计算深度的简单方法:

    public static int GetDepth(Item item)
    {
        int d = 0;
        while ((item = item.Parent) != null)
            d++;
        return d;
    }
    

    以后的查询非常简单

    var results = from item in data
                  let depth = GetDepth(item)
                  orderby depth descending
                  select item;
    

    如果您的数据结构不同,那么只编写一个LINQ to Objects查询会很容易,并且Parent有所有子项的链接。在这种情况下,查询数据更容易,因为每个项目都有一组依赖项,而LINQ适用于集合,对象是单个项目,例如Parent属性。我刚才写了一篇关于querying hierarchy of objects using LINQ的博客文章,你可能会觉得它很有趣。

答案 1 :(得分:2)

虽然我的朋友们已经发布了一些好的答案,但我还是愿意提供另一个答案。如果更改节点结构以获得更好的性能是可行的,则可以为每个节点设置一次深度属性,然后根据depth属性对它们进行排序。

public class Node
{
    public Node()
    {
        this.Items = new List<Node>();
        this.Parent = null;
        this.Depth = 0;
    }
    public Node Parent { get; set; }
    public List<Node> Items { get; set; }
    public int Depth { get; set; }
}

public void SetDepths(Node node, int depth)
{
    node.Depth = depth;
    foreach (var child in node.Items)
        SetDepths(child, depth + 1);
}
public void Sort(List<Node> nodes)
{
    SetDepths(nodes.Single(x => x.Parent == null), 1);
    nodes = nodes.OrderBy(x => x.Depth).ToList();
}

应该调用递归函数,如:

SetDepths(rootNode, 1);

在这里它在sort方法中调用,或者你选择任何其他地方来做。

答案 2 :(得分:0)

有几种方法可以解决这个问题。

首先,你可以实现方法,f.e。 AsFlattenEnumerable。在映像中,您拥有类Tree<T>的类T的类Flatten<T>

声明课程public Flatten<T> { public T Data { get; } public int Level { get; } }

AsFlattenEnumerable

然后实施public class Tree<T> { . . . public IEnumerable<Flatten<T>> AsFlattenEnumerable() { . . . } . . . }

var ordereNodes = from node in tree.AsFlattenEnumerable()
                  // F.e. first Level, then A, then B
                  order node by new { node.Level, node.Data.A, node.Data.B };

现在您可以使用简单的LINQ查询对节点进行排序:

order by

其次,C#编译器允许您为OrderBy子句(以及Tree<T>方法)声明自己的方法。

成像,你有一个实现ITreeEnumerable<T>接口的System.Web类(在Where程序集中有类似的分层接口)。

然后,您可以定义自己的扩展方法,例如OrderBypublic static ITreeEnumerable<TSource> OrderBy<TSource, TKey>(this ITreeEnumerable<TSource> source, Func<TSource, TKey> keySelector) { . . . }

var orderedNodes = from node in tree // tree implements ITreeEnumerable<T>
                   // First Level (by default), then custom ordering by A and B
                   order by new { node.A, node.B };
. . .
var enumerableNodes = orderedNodes.AsEnumerable();

现在您可以使用LINQ对进行排序:

var result = from i in obj
             order i by i.Foo;

C#编译器转换此表达式:

var result = obj.OrderBy(i => i.Key);

到此:

obj

IEnumerable变量可以是任何类型,不一定是objOrderBy的类型应该有<button class="btn btn-default" data-dismiss="modal" id="btndialog" >DIALOG</button> 方法,或者您可以实现扩展方法。

答案 3 :(得分:0)

无耻的插件 - 您可以添加我一直在使用的名为Treenumerable的Nuget包:

  

https://www.nuget.org/packages/Treenumerable

     

https://github.com/jasonmcboyd/Treenumerable

您必须实现一个知道如何遍历树的ITreeWalker接口(两种方法)。一旦你这样做,你就可以这样做:

// Pseudocode
TreeWalker walker = new TreeWalker();
IEnumerable<YourType> levelOrder = walker.LevelOrderTraversal(A);

您还可以获得十几个(如果计算重载次数更多)或其他枚举/查询树结构的方法。当前版本 - 1.2.0 - 具有良好的测试覆盖率,并且似乎非常稳定。