【问题标题】:Querying nested collection (parent / children)查询嵌套集合(父/子)
【发布时间】:2017-01-10 23:01:05
【问题描述】:

我正在尝试使用 linq 获取满足这些条件的所有菜单和子项:

  • 菜单应具有链接或子项计数 > 0 以显示在
    网站
  • 对于有子菜单:它们应该至少有一个带有链接的子菜单

这是菜单类:

public class Menu
{
    public string Name { get; set; }
    public string Link { get; set; }

    public List<Menu> Children { get; set; }

    public  Menu()
    {
        Children = new List<Menu>();
    }
}

假设我们有这样的数据结构:

        List<Menu> root = new List<Menu>();
        Menu parent_1 = new Menu() { Name = "Parent 1", Link = null };
        Menu parent_2 = new Menu() { Name = "Parent 2", Link = null };


        //children for parent 1
        Menu p1_child_1 = new Menu() { Name = "p1_child_1", Link = null };
        Menu p1_child_2 = new Menu() { Name = "p1_child_2", Link = null };
        //sub children of p1_child_2
        Menu p1_child_1_1 = new Menu() { Name = "p1_child_1_1", Link = "l1-1" };
        Menu p1_child_1_2 = new Menu() { Name = "p1_child_1_2", Link = null };

        p1_child_1.Children.AddRange(new List<Menu> { p1_child_1_1 , p1_child_1_2 });
        parent_1.Children.AddRange(new List<Menu> { p1_child_1, p1_child_2 });


        Menu p2_child_1 = new Menu() { Name = "p2_child_1", Link = null };
        Menu p2_child_2 = new Menu() { Name = "p2_child_2", Link = "l2-2" };

        Menu p2_child_1_1 = new Menu() { Name = "p2_child_1_1", Link = null };
        Menu p2_child_1_2 = new Menu() { Name = "p2_child_1_2", Link = null };

        p2_child_1.Children.AddRange(new List<Menu> { p2_child_1_1, p2_child_1_2 });


        parent_2.Children.AddRange(new List<Menu> { p2_child_1, p2_child_2 });

        root.Add(parent_1);
        root.Add(parent_2);

结果:根据请求的条件返回的过滤列表将是:

父_1

  • p1_child_1

    • p1_child_1_1

父_2

  • p2_child_2

考虑到菜单可能有多个级别,如何使用 Linq 或替代方法来实现这一点?

尝试了 cmets 中提出的解决方案,我添加了扩展方法

 public static IEnumerable<TResult> SelectHierarchy<TResult>(this IEnumerable<TResult> source, Func<TResult, IEnumerable<TResult>> collectionSelector, Func<TResult, bool> predicate)
    {
        if (source == null)
        {
            yield break;
        }
        foreach (var item in source)
        {
            if (predicate(item))
            {
                yield return item;
            }
            var childResults = SelectHierarchy(collectionSelector(item), collectionSelector, predicate);
            foreach (var childItem in childResults)
            {
                yield return childItem;
            }
        }

然后调用方法:

var result = root.SelectHierarchy(n => n.Children, n => n.Children.Count > 0 || n.Link != null).ToList();

但这不是我想要的,我希望有两个菜单带有满足我条件的子菜单,但我得到了 6 个我猜是扁平的菜单。

虽然,p2_child_1 被返回,因为孩子数 > 0,但它不应该导致它的菜单没有链接。 (我把谓词放在上面,因为我没有其他选择。

【问题讨论】:

  • Linq 不是为递归查询而设计的。您必须创建一些递归迭代的迭代器,然后查询 that。如果你有一些有用的东西,Linq 不会有很大帮助。
  • 那么无论linq如何,如何以有效的方式做到这一点
  • 您能否提供示例数据和预期输出作为有效的 C# 代码?因此,例如,如果您想将两个数字相加,您可以向我们提供:int x = 3; int y = 5;,我想要结果 int z = 8。然后,我们可以确定,当我们提供代码 x + y 时,我们知道我们会为您提供您想要的结果。
  • @Enigmativity 查看示例数据和期望的结果,很明显(对我而言)OP 有一个包含根节点的列表,并且希望获得一个新列表,其中删除了一些节点(如child 1child 1.1)。他还要求LINQ 或替代方法。但无论如何,我想你知道得更好:)
  • @IvanStoev - 我很高兴得到纠正,但 OP 需要权衡他的实际要求。如果他 ping 我,我会在需要时重新打开。

标签: c# linq


【解决方案1】:

这对我有用:

public static class Ex
{
    public static List<Menu> CloneWhere(this List<Menu> source, Func<Menu, bool> predicate)
    {
        return
            source
                .Where(predicate)
                .Select(x => new Menu()
                {
                    Name = x.Name,
                    Link = x.Link,
                    Children = x.Children.CloneWhere(predicate)
                })
                .Where(predicate)
                .ToList();
    }
}

示例数据如下所示:

...那么我可以应用这个:

var result = root.CloneWhere(m => m.Children.Any() || m.Link != null);

...我得到了这个结果:

【讨论】:

  • 感谢@Enigmativit。这是一个很好的说明,但是在我的情况下,不应返回“p2_child_1”,因为它没有链接也没有孩子(因为它的孩子已经没有链接并且被删除了)。这是我的担心。
  • @h.salman - 您的示例代码具有p2_child_1.Children.AddRange(new List&lt;Menu&gt; { p2_child_1_1, p2_child_1_2 });,因此该菜单具有子项。不对吗?
  • @Slai - p2_child_1 在源数据中有子项。过滤是否适用于最终结果?
  • @h.salman - 我已经更新了查询以过滤重新构建后的内容。
  • @Enigmativity,是的,正如“Slai”所提到的,它不应该出现在结果中,因为它没有孩子并且它的链接是空的(在第一种情况下它有孩子,但是孩子和它自己没有链接,所以必须删除所有链接)。我已经在示例结果中说明了这一点。谢谢你的帮助..
【解决方案2】:

假设深度没有太大导致堆栈溢出,可以使用简单的递归方法或递归lambda,如下:

Func<List<Menu>, List<Menu>> filter = null;
filter = items =>
    (from item in items
     let children = filter(item.Children)
     where item.Link != null || children.Any()
     select new Menu { Name = item.Name, Link = item.Link, Children = children }
    ).ToList();
var filtered = filter(root);

关键部分是在过滤父级之前先处理子级(后序遍历)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-10-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-12
    • 2018-08-02
    相关资源
    最近更新 更多