2013-08-01 2 views
1

У меня есть иерархическое меню. Пункты меню выглядит следующим образом:Обработка иерархической коллекции с помощью Linq

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 
... 

Так что результат является то, что я ожидал. Но теперь я хочу получить только MenuElement со свойством IsMandatory == false AND THEIR PARENTS (и их grand-parents и т. Д.). Например, в приведенном выше меню только 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

+0

Я еще не прочитал весь ваш вопрос, но если вы собираетесь создать изменчивую структуру для 'MenuElement', у вас будет плохое время. EDIT: Не могли бы вы его обновить, чтобы при вызове 'BuildMenu' каждому« узлу »была назначена прямая ссылка на родительский узел? Тогда становится тривиальнее ползать по дереву. –

+0

Да, в BuildMenu каждому узлу присваивается его родительский элемент с свойством ParentId, а также заполняются childrenNodes. – Florian

+0

@Florian вы могли бы улучшить условия фильтрации? Вы сказали: «... со свойством IsMandatory == false AND THEIR PARENTS ...», я могу понять первое предложение, но как насчет «... И ИХ РОДИТЕЛЕЙ ...»? –

ответ

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); 
    } 
} 
+0

Спасибо, я проверяю это ;-) – Florian

Смежные вопросы