【问题标题】:Handling hierarchical collection with Linq使用 Linq 处理分层集合
【发布时间】:2013-08-01 12:39:18
【问题描述】:

我有一个分层菜单。菜单项如下所示:

public struct MenuElement
{
    public int Id {get; set;}
    public int Position {get; set;}
    public string Label {get; set;}
    public int HierarchicalLevel {get; set;}
    public int ParentLevelId {get; set;}
    public bool IsMandatory {get; set;}
}

我的菜单结构由节点类管理:

public class Node<T>
{
   public T Item {get; set;}
   public IEnumerable<Node<T>> ChildrenNodes {get; set;}
   public int HierarchicalLevel {get; set;}
}

从数据库中检索菜单项。因此,当我构建菜单并浏览菜单时:

// Get the menu items from database
List<MenuElement> flattenMenuElements = GetMenuItemsFromDb();

// Build the hierarchical menu with relationships between nodes (parentId, ChildrenNodes...)
List<Node<MenuElement>> hierarchicalMenu = BuildMenu(flattenMenuElements);

foreach(var node in Browse(hierarchicalMenu))
{
     string space = ""

     for(int i=0; i<node.HierarchicalLevel; i++)
          space = String.Concat(space, " ");

     Console.Writeline("{0}{1}. {2}",space, node.Item.Position, node.Item.Label);
}

//Browse method
IEnumerable<Node<MenuElement>> Browse(IEnumerable<Node<MenuElement>> nodes)
{
     foreach(var node in nodes)
     {
         yield return node;
         foreach (var childNode in Browse(node.ChildrenNodes))
         {
              yield return childNode;
         }
     }
}

控制台输出:

1. LabelMenu1
 1. LabelMenu11
  1. LabelMenu111
  2. LabelMenu112
  3. LabelMenu113
 2. LabelMenu12
 3. LabelMenu13
2. LabelMenu2
3. LabelMenu3
 1. LabelMenu31
...

所以结果是我所期望的。但是现在我只想获取具有属性 IsMandatory == false 和他们的父母(以及他们的祖父母等)的 MenuElement。例如,在上面的菜单中,只有 LabelMenu112 和 LabelMenu31 的 IsMandatory 属性设置为 false。所以我想如果我浏览我的菜单,输出结果会是这样的:

1. LabelMenu1
 1. LabelMenu11
  2. LabelMenu112
3. LabelMenu3
 1. LabelMenu31

实际上,为此,我在过滤了我想要保留的菜单元素后,从第一个分层菜单重建了第二个菜单:

// Get the menu elements from database
List<MenuElement> flattenMenuElements = GetMenuItemsFromDb();

// Build the hierarchical menu with relationships between nodes (parentId, ChildrenNodes...)
List<Node<MenuElement>> hierarchicalMenu = BuildMenu(flattenMenuElements);

// Get a flat list of menu elements where the property IsMandatory is set to false
List<MenuElement> filteredMenuElements = flattenMenuElements.Where(m => m.IsMandatory == false).ToList();

// Get a flat list of filtered menu elements AND THEIR PARENTS, GRAND PARENTS etc
List<MenuElement> filteredMenuElementsWithParents = GetMenuElementsWithParents(hierarchicalMenu, filteredMenuElements).ToList();


List<MenuElement> GetMenuElementsWithParents(IEnumerable<Node<MenuElement>> hierarchicalMenu, IEnumerable<MenuElement> filteredMenuElements)
{
     List<MenuElement> menu = new List<MenuElement>();
     foreach (var item in filteredMenuElements)
     {
          menu.Add(item);
          AddParentNode(item, menu, hierarchicalMenu);
     }
}

void AddParentNode(MenuElement element, List<MenuElement> menu, IEnumerable<Node<MenuElement>> hierarchicalMenu)
{
    if (element.ParentLevelId != default(int))
    {
        // Get the parent node of element
        MenuElement menuEl = Browse(hierarchicalMenu)
                               .Where(node => node.Item.Id == element.ParentLevelId)
                               .Select(node => node.Item)
                               .First();

        if(!menu.Contains(menuEl))
            menu.Add(menuEl);

        AddParentNode(menuEl, menu, hierarchicalMenu);             
    }
}

此解决方案有效,但我想知道是否可以使用 linq 的强大功能来检索此过滤后的菜单元素及其父项,而不是构建平面列表,然后从平面列表中构建第二个分层菜单。

有没有办法使用 Linq 做到这一点?

谢谢!

问候,

弗洛里安

【问题讨论】:

  • 我还没有读完你的整个问题,但是如果你要为MenuElement 做一个可变结构,你会很糟糕。编辑:您能否更新它,以便在您调用BuildMenu 时,为每个Node 分配对父节点的直接引用?然后爬上树就变得微不足道了。
  • 是的,在 BuildMenu 中,每个节点都通过 ParentId 属性分配给其父节点,并且也填充了 childrenNodes。
  • @Florian 你能让你的过滤条件更清晰吗?你说... with the property IsMandatory == false AND THEIR PARENTS ...,我可以理解第一个子句,但是...AND THEIR PARENTS ...呢?

标签: c# linq collections hierarchical-data


【解决方案1】:

使用这个Node class会更容易。

Node<MenuElement> rootNode = <Any node of the collection>.Root;

var mandatoryNodes = rootNode.SelfAndDescendants.Where(n => n.Value.IsMandatory);

foreach (var mandatoryNode in mandatoryNodes)
{
    foreach (var node in mandatoryNode.SelfAndAncestors.Reverse()) // Reverse because you want from root to node
    {
        var spaces = string.Empty.PadLeft(node.Level); // Replaces: for(int i=0; i<node.HierarchicalLevel; i++) space = String.Concat(space, " ");
        Console.WriteLine("{0}{1}. {2}", spaces, node.Value.Position, node.Value.Label);
    } 
}

【讨论】:

  • 谢谢,我测试一下;-)
猜你喜欢
  • 2011-06-29
  • 1970-01-01
  • 1970-01-01
  • 2010-09-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多