使用LINQ递归查询返回分层组集

8

给定以下模型列表

public class Team
{
    public int TeamId { get; set; }
    public int ParentTeamId { get; set; }
}

我想编写一个递归的linq查询,以便获取类似于以下结构的层次结构

Team
    ChildTeams
Team
    Team
        ChildTeams

我尝试了许多方法并看到了许多类似的问题,但都没有特别帮助我解决问题。我最近尝试的一种方法是这样的:

private class TeamGrouping
{
    public int? ParentTeamId { get; set; }
    public IEnumerable<Team> ChildTeams { get; set; }
    public IEnumerable<TeamGrouping> Grouping { get; set; }
}

private IEnumerable<TeamGrouping> ToGrouping(IEnumerable<Team> teams)
{
    return teams.GroupBy(t => t.ParentTeamId, (parentTeam, childTeams) => new TeamGrouping {ParentTeamId = parentTeam, ChildTeams = childTeams});
}

private IEnumerable<TeamGrouping> ToGrouping(IEnumerable<TeamGrouping> teams)
{
    return teams.GroupBy(t => t.ParentTeamId, (parentTeam, childTeams) => new TeamGrouping{ParentTeamId = parentTeam, Grouping = childTeams});
}

我会将团队列表传递给第一个 ToGrouping(IEnumerable<Team>),然后将返回的分组传递给 ToGrouping(IEnumerable<TeamGrouping>),但这样会产生不正确的结果。
有人有什么建议或想法吗?

所以你从一个扁平的团队集合开始,想要创建一棵树?我认为你需要更多的东西,不仅仅是LINQ(虽然我很有兴趣被证明错误)。当你遍历你的团队集合时,你需要构建出一棵树,而不是一个IGrouping<T>的集合。 - Mister Epic
抱歉,是的,我应该提到它是一个扁平的团队列表。 - ChrisO
3个回答

6

首先,您的TeamGrouping实际上比需要的要复杂一些。它只需要Team对象和自身的一个儿子序列即可:

public class TeamNode
{
    public Team Value { get; set; }
    public IEnumerable<TeamNode> Children { get; set; }
}

接下来,我们将为每个团队创建一个节点。然后,我们将使用 ToLookup 将它们按父 ID 进行分组。(GroupBy 的用法和这个非常接近,但是 ToLookup 将更容易一些。)最后,我们只需将每个节点的子项设置为该节点的查找值(请注意,ILookup 如果键不存在则返回空序列,因此我们的叶子将被完美处理)。最后,我们可以通过查找所有父 ID 为 null 的节点来返回所有顶层节点。

public static IEnumerable<TeamNode> CreateTree(IEnumerable<Team> allTeams)
{
    var allNodes = allTeams.Select(team => new TeamNode() { Value = team })
        .ToList();
    var lookup = allNodes.ToLookup(team => team.Value.ParentTeamId);
    foreach (var node in allNodes)
        node.Children = lookup[node.Value.TeamId];
    return lookup[null];
}

1
首先,您需要像这样的一个对象,因此团队对象可能是:
public class Team
{
    public int? ParentId { get; set; }
    public IEnumerable<Team> ChildTeams { get; set; }
}

然后是一个递归函数:
private IEnumerable<Team> BuildTeams(IEnumerable<Team> allTeams, int? parentId)
{
    var teamTree = new List<Team>();
    var childTeams = allTeams.Where(o => o.ParentId == parentId).ToList();

    foreach (var team in childTeams)
    {
        var t = new Team();
        var children = BuildTeams(allTeams, team.TeamID);
        t.ChildTeams = children;
        teamTree.Add(t);
    }

    return teamTree ;
}

第一个调用传递了一个null给parent,会提取所有父级为null的团队 :), 不过我注意到你的团队没有父级为null的,所以不确定你目前如何识别顶层团队?

这个很好用,但我不明白为什么你要创建一个新的Team对象,这样你会得到所有属性都是null的情况,为什么不直接传递team对象呢? - Martin
@fernandospr,你能否考虑不要进行这些无意义的非编辑操作吗?这样会毫无理由地推动旧问题。 - Caius Jard
1
@CaiusJard SO提示用户进行评论。 - fernandospr

0

当您使用Entity Framework对象时,另一种解决方案是通过在第一次调用时删除具有空父级的Team对象来处理根对象(Parent == null)。

public class Team
{
    int TeamID { get; set; }
    Team Parent; { get; set; }
}

public class TeamNode
{
    public Team Node { get; set; }
    public IEnumerable<TeamNode> Children { get; set; }
}

private List<Team> BuildTeams(List<Team> allTeams, int? parentId)
{
    List<TeamNodes> teamTree = new List<Team>();
    List<Team> childTeams;

    if (parentId == null)
    {
        childTeams = allTeams.Where(o => o.Parent == null).ToList();
        allTeams.RemoveAll(t => t.Parent == null);
    }
    else
    {
        childTeams = allTeams.Where(o => o.Parent.ID == parentId).ToList();
    }

    foreach (Team team in childTeams)
    {
        TeamNode teamNode = new Team();
        teamnode.Node = team;
        List<TeamNode> children = BuildTeams(allTeams, team.TeamID);
        teamNode.ChildTeams = children;
        teamTree.Add(t);
    }

    return teamTree ;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接