首页 > 解决方案 > 在 VB.net 中使用 Linq 获取节点的所有子节点

问题描述

我有用于在 VB.Net 中填充 TreeList 的分层数据表,如下所示:

ID   ParentID    Name
----------------------
1    NUll         a
2    NUll         b
3    2            c
4    1            d
5    3            e
6    5            f
7    6            g
8    5            h

我的问题:如何在 vb.net 中使用 Linq 获取节点(ID)的所有子节点列表?请帮我。

标签: vb.netlinqhierarchical-data

解决方案


不幸的是,我对 VB 的了解不够好,无法为您提供 VB 答案。我将在 C# 中给出答案。你可能会理解这个想法。也许你可以添加你的 VB 翻译?

你忘了描述你的 Node 类。我假设您不仅想要孩子,还想要孙子(等)。

class TreeNode
{
    public int Id {get; set;}
    public string Name {get; set;}

    // every node has zero or more sub-nodes:
    public IEnumerable<TreeNode> Nodes {get; set;}
}

您希望您的层次结构达到未知的深度。因此,您不能使用标准 LINQ 函数。但是您可以轻松地扩展 LINQ 来创建自己的。请参阅扩展功能揭秘

public static IEnumerable<TreeNode> AsTreeNodes(this IEnumerable<Person> persons)
{
    // Top TreeNodes: all persons without a Parent:
    return persons.AsTreeNodes((int?)null);
}

使用递归返回所有具有 parentId 的人的节点序列的实际函数:

public static IEnumerable<TreeNode> AsTreeNodes(this IEnumerable<Person> persons, int? parentId)
{
    // Top Nodes: all persons with parentId
    var personsWithParentId = persons.Where(person.ParentId == parentId);

    foreach (var person in personsWithParentId)
    {
        // every person will become one TreeNode with sub-nodes using recursion
        TreeNode node = new TreeNode()
        {
            Id = person.Id,
            Name = person.Name,

            // get all my Children and Children's children:
            Nodes = persons.ToNodeCollection(person.Id),
        };
        yield return node;
    }
}

请注意,我选择返回 IEnumerable 而不是 List / Array。这样,只有在您真正枚举它们时才会创建项目。因此,如果您不想使用 ID 为 1 的人的节点,则不会创建他的所有孩子。

用法:获取“乔治华盛顿”所有后代的家族等级:

IEnumerable<Person> allPersons = ...
IEnumerable<TreeNode> allFamilyHierarchies = allPersons.AsTreeNodes();
IEnumerable<TreeNode> washingtonFamily = allFamilyHierarchies
    .Where(familyHierarchy => familyHierarchy.Name == "George Washington");

注意:到目前为止,还没有创建 TreeNode,只创建了 IEnumerable。只要您执行不返回 IEnumerable 的操作,例如foreachToListAny、 ,枚举就会开始FirstOrDefault

因此,所有非华盛顿族将被忽略,不会创建顶级节点,也不会为其创建子节点。

如果您仔细观察,您会发现要查找所有子项,我必须枚举整个集合,以查找具有某个 ParentId 的所有人员。如果您首先将所有人员分组到具有相同 ParentId 的组中,然后将这些组放入以 ParentId 作为键的字典中,则可以更有效地完成此操作。这样可以非常快速地找到 ID 为 X 的父级的所有子级:

var personsWithSameParentId = persons.GroupBy(person => person.ParentId);
var dictionary = personsWithSameParentId.ToDictionary(group => group.Key)

字典中的每个元素都具有与 parentId 相等的键,并且所有具有此 parentId 的人都作为元素。

TreeNode CreateNodeForPerson(Person person)
{
    IEnumerable<Person> children = dictionary[person.Id];
    IEnumerable<TreeNode> childNodes = children
        .Select(child => CreateNodeforPerson(child));

    return new TreeNode()
    {
        Id = person.Id,
        Name = person.Name,
        Nodes = childNodes,
     };
}

你会看到同样的递归。但是一旦你有了字典,你就不必枚举完整的 Person 集合,你只需访问你正在为其创建点头的 Person 的孩子/孩子的孩子等。


推荐阅读