使用Linq处理分层收集
本文关键字:分层 处理 Linq 使用 | 更新日期: 2023-09-27 18:25:45
我有一个分层菜单。菜单项如下所示:
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的方法可以做到这一点?
谢谢!
问候,
Florian
使用这个Node类会更容易。
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);
}
}