2010-09-21 13 views
5

Digamos que tenemos una clase¿Renderizar una jerarquía usando LINQ?

Category 
{ 
    ID, 
    Name, 
    ParentID 
} 

y una lista

1, 'Item 1', 0 
2, 'Item 2', 0 
3, 'Item 3', 0 
4, 'Item 1.1', 1 
5, 'Item 3.1', 3 
6, 'Item 1.1.1', 4 
7, 'Item 2.1', 2 

Podemos usar LINQ para hacer un árbol como:

Item 1 
Item 1.1 
    Item 1.1.1 
Item 2 
Item 2.1 
Item 3 
Item 3.1 

Cualquier ayuda se agradece!

Respuesta

5

Aquí está el "LINQ "solo" versión:

Func<int, int, string[]> build = null; 
build = (p, n) => 
{ 
    return (from x in categories 
      where x.ParentID == p 
      from y in new[] 
      { 
       "".PadLeft(n)+ x.Name 
      }.Union(build(x.ID, n + 1)) 
      select y).ToArray(); 
}; 
var lines = build(0, 0); 

Sí, es recursivo LINQ.


solicitud por NVA 's, aquí es la manera de hacer que todos los registros "huérfanos" se convierten en registros de raíz:

Func<IEnumerable<int>, int, string[]> build = null; 
build = (ps, n) => 
{ 
    return (from x in categories 
      where ps.Contains(x.ParentID) 
      from y in new[] 
    { 
     "".PadLeft(n)+ x.Name 
    }.Union(build(new [] { x.ID }, n + 1)) 
      select y).ToArray(); 
}; 

var roots = (from c in categories 
      join p in categories on c.ParentID equals p.ID into gps 
      where !gps.Any() 
      orderby c.ParentID 
      select c.ParentID).Distinct(); 

var lines = build(roots, 0); 
+0

Muchas gracias por sus ayuda. Funciona muy bien ^^ – ByulTaeng

+0

Por cierto, si ParentID de un artículo no está en la lista, se excluirá, ¿podría ayudarme de nuevo? ¡Muchas gracias! – ByulTaeng

+0

Mi respuesta ya los excluye si el 'ParentID' no está en la lista. ¿Estás pidiendo que se incluya? Y, de ser así, ¿desea que estos "huérfanos" se muestren en el nivel raíz (como si su 'ParentID' fuera' 0')? – Enigmativity

0

Puede utilizar la recursividad:

public class Category 
{ 
    public int ID { get; set; } 
    public string Name { get; set; } 
    public int ParentID { get; set; } 
    public List<Category> Children { get; set; } 
} 

class Program 
{ 
    static void Main() 
    { 
     List<Category> categories = new List<Category>() 
     { 
      new Category() { ID = 1, Name = "Item 1", ParentID = 0}, 
      new Category() { ID = 2, Name = "Item 2", ParentID = 0 }, 
      new Category() { ID = 3, Name = "Item 3", ParentID = 0 }, 
      new Category() { ID = 4, Name = "Item 1.1", ParentID = 1 }, 
      new Category() { ID = 5, Name = "Item 3.1", ParentID = 3 }, 
      new Category() { ID = 6, Name = "Item 1.1.1", ParentID = 4 }, 
      new Category() { ID = 7, Name = "Item 2.1", ParentID = 2 } 
     }; 

     List<Category> hierarchy = new List<Category>();       
     hierarchy = categories 
         .Where(c => c.ParentID == 0) 
         .Select(c => new Category() { ID = c.ID, Name = c.Name, ParentID = c.ParentID, Children = GetChildren(categories, c.ID) }) 
         .ToList(); 

     HieararchyWalk(hierarchy);    

     Console.ReadLine(); 
    }   

    public static List<Category> GetChildren(List<Category> categories, int parentId) 
    {    
     return categories 
       .Where(c => c.ParentID == parentId) 
       .Select(c => new Category { ID = c.ID, Name = c.Name, ParentID = c.ParentID, Children = GetChildren(categories, c.ID) }) 
       .ToList(); 
    } 

    public static void HieararchyWalk(List<Category> hierarchy) 
    { 
     if (hierarchy != null) 
     { 
      foreach (var item in hierarchy) 
      { 
       Console.WriteLine(string.Format("{0} {1}", item.ID, item.Name)); 
       HieararchyWalk(item.Children);      
      } 
     } 
    }   
} 
4

Estos métodos de extensión hacen exactamente lo que quiere:

public static partial class LinqExtensions 
{ 
    public class Node<T> 
    { 
     internal Node() { } 

     public int Level { get; internal set; } 
     public Node<T> Parent { get; internal set; } 
     public T Item { get; internal set; } 
     public IList<Node<T>> Children { get; internal set; } 
    } 

    public static IEnumerable<Node<T>> ByHierarchy<T>(
     this IEnumerable<T> source, 
     Func<T, bool> startWith, 
     Func<T, T, bool> connectBy) 
    { 
     return source.ByHierarchy<T>(startWith, connectBy, null); 
    } 

    private static IEnumerable<Node<T>> ByHierarchy<T>(
     this IEnumerable<T> source, 
     Func<T, bool> startWith, 
     Func<T, T, bool> connectBy, 
     Node<T> parent) 
    { 
     int level = (parent == null ? 0 : parent.Level + 1); 

     if (source == null) 
      throw new ArgumentNullException("source"); 

     if (startWith == null) 
      throw new ArgumentNullException("startWith"); 

     if (connectBy == null) 
      throw new ArgumentNullException("connectBy"); 

     foreach (T value in from item in source 
          where startWith(item) 
          select item) 
     { 
      var children = new List<Node<T>>(); 
      Node<T> newNode = new Node<T> 
      { 
       Level = level, 
       Parent = parent, 
       Item = value, 
       Children = children.AsReadOnly() 
      }; 

      foreach (Node<T> subNode in source.ByHierarchy<T>(possibleSub => connectBy(value, possibleSub), 
                   connectBy, newNode)) 
      { 
       children.Add(subNode); 
      } 

      yield return newNode; 
     } 
    } 

    public static void DumpHierarchy<T>(this IEnumerable<Node<T>> nodes, Func<T, string> display) 
    { 
     DumpHierarchy<T>(nodes, display, 0); 
    } 

    private static void DumpHierarchy<T>(IEnumerable<LinqExtensions.Node<T>> nodes, Func<T, string> display, int level) 
    { 
     foreach (var node in nodes) 
     { 
      for (int i = 0; i < level; i++) Console.Write(" "); 
      Console.WriteLine (display(node.Item)); 
      if (node.Children != null) 
       DumpHierarchy(node.Children, display, level + 1); 
     } 
    } 

} 

Usted las puede utilizar de la siguiente manera:

categories.ByHierarchy(
     cat => cat.ParentId == null, // assuming ParentId is Nullable<int> 
     (parent, child) => parent.Id == child.ParentId) 
    .DumpHierarchy(cat => cat.Name); 
0
public IEnumerable<HelpPageMenuItem> GetHelpPageMenuItems() 
    { 
     var helpPages = (from h in Context.HelpPages select new HelpPageMenuItem{HelpPageId = h.HelpPageId, ParentHelpPageId = h.ParentHelpPageId, PageContext = h.PageContext, MenuText = h.MenuText}).ToList(); 
     var parents = from h in helpPages where !h.ParentHelpPageId.HasValue select PopulateChildren(h, helpPages); 
     return parents.ToList(); 
    } 

    private static HelpPageMenuItem PopulateChildren(HelpPageMenuItem helpPageMenuItem, IEnumerable<HelpPageMenuItem> helpPages) 
    { 
     helpPageMenuItem.ChildHelpPages = 
      (from h in helpPages 
      where h.ParentHelpPageId == helpPageMenuItem.HelpPageId 
      select PopulateChildren(h, helpPages)).ToList(); 

     return helpPageMenuItem; 
    } 
0
@model List<OrgChart.Models.Node> 
    @{  

    Func<int?, List<OrgChart.Models.Node>, string> recuresive = null; 
recuresive = (parentid, list) => string.Join("", list.Where(x => x.ParentId == parentid).Select(x => "<li>" + x.Name + "<ul>" + recuresive(x.Id, list.Where(y => y.ParentId != parentid).ToList()) + "</ul></li>")); 
    } 
    @Html.Raw("<ul id='org1' >" + recuresive(null, Model) + "</ul>") 
    <div id="chart" class="orgChart"></div> 
0
 public static List<TSource> BuildTreeView<TSource, TKey>(this List<TSource> allItems 
     , Func<TSource, TKey> parentSelector, Func<TSource, TKey> childSelector, Expression<Func<TSource, List<TSource>>> childrenPropertySelector 
     , Func<TSource, bool> GetRoot, List<TSource> rootList = null) 
    { 

     if (rootList == null) 
      rootList = allItems.Where(GetRoot).ToList(); 
     if (rootList != null && rootList.Count > 0) 
     { 
      rootList.ForEach(rootItem => 
      { 
       Func<TSource, bool> whereClause = x => childSelector(rootItem).Equals(parentSelector(x)); 
       var childrenProperty = (childrenPropertySelector.Body as MemberExpression).Member as System.Reflection.PropertyInfo; 
       var childrenList = allItems.Where(whereClause).ToList(); 
       childrenProperty.SetValue(rootItem, childrenList); 

       if (childrenList.Count > 0) 
        BuildTreeView(allItems, parentSelector, childSelector, childrenPropertySelector, GetRoot, childrenProperty.GetValue(rootItem) as List<TSource>); 
      }); 

     } 
     return rootList; 
    } 

// llamada al método

List<Channel> rootChannel = listChannel.BuildTreeView(f => f.PARENT_CODE, x => x.CODE, z => z.SubChannels, c => c.CODE == "AC"); 
Cuestiones relacionadas