查询嵌套集合(父/子)

时间:2017-01-09 20:21:05

标签: c# linq

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

  • 菜单应该有链接或儿童计数> 0显示在 网站
  • 对于有孩子的菜单:他们应该至少有一个带有链接的子菜单

这是菜单类:

Option Explicit

Public WithEvents clntFldrItms As Outlook.Items

Private Sub Application_Startup()
    Dim clntFldr As MAPIFolder
    Set clntFldr = Application.Session.GetDefaultFolder(olFolderSentMail).Folders("Client Emails")
    Set clntFldrItms = clntFldr.Items
    Set clntFldr = Nothing
    Debug.Print clntFldrItms.item(1).Subject
End Sub

Private Sub clntFldrItms_ItemAdd(ByVal item As Object)
    Dim bChar As String
    bChar = "\/:*?™""® <>|.&@#_+`©~;-+=^$!,'" & Chr(34)
    Dim saveName As String
    If item.Class = olMail Then
        saveName = item.Subject
        For x = 1 To Len(bChar)
            saveName = Replace(saveName, Mid(bChar, x, 1), "-")
        Next x
        item.SaveAs "C:\Users\User\Google Drive\8 - VBA work\Preparation for Assisted Responder\Sent Messages Folder\" & _
        saveName & ".msg", olMSG
    End If
End Sub

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

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

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

  

parent_1

  • p1_child_1

    • p1_child_1_1
  

parent_2

  • p2_child_2

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

尝试使用评论中提出的解决方案,我添加了扩展方法

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

然后调用方法:

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

然而,这不是我想要的,我期待两个带有子菜单的菜单满足我的条件,但我得到了6个菜单,我猜是平的。

enter image description here

虽然,因为儿童数量计算后返回了var result = root.SelectHierarchy(n => n.Children, n => n.Children.Count > 0 || n.Link != null).ToList(); &gt; 0,但它不应该导致其菜单没有链接。 (我把谓词放在上面,因为我没有其他选择。

2 个答案:

答案 0 :(得分:2)

这对我有用:

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

示例数据如下所示:

source

...然后我可以申请:

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

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

result

答案 1 :(得分: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);

关键部分是在过滤父母之前首先处理孩子(发布命令遍历)。