0

I have a database table like shown below:

MenuItemID       |    int   [Primary Key]   1 -----+
MenuItemName     |    nvarchar(100)                |
ParentID         |    int                   * -----+

As you can see in the above schematic diagram, ParentID column refers to MenuItemID due to which we can obtain hierarchical outpu.

The sample data in above table is as shown below:

enter image description here

From the sample data above, I want the output like:

Electronics
    Mobile
    Desktop
    Laptop
        Lenovo
        Dell
Sports
    Cricket
    Football
    Hockey
Stationary
    Books
    Pens
    Pencils
    Erasers

What I have tried:

I have tried the below mentioned code, where I tried to use GroupJoin extension method to achieve the required output:

class Program
{
    static void Main(string[] args)
    {
        OnlineShoppingEntities db = new OnlineShoppingEntities();

        var x = db.MenuItems.GroupJoin(db.MenuItems,
                                       m => m.MenuItemID,
                                       m => m.ParentID,
                                       (parentMenuItems, childMenuItems) => new
                                       {
                                           ParentMenuItems = parentMenuItems,
                                           ChildMenuItems = childMenuItems
                                       });

        foreach (var v in x)
        {
            Console.WriteLine(v.ParentMenuItems.MenuItemName);
            foreach (var m in v.ChildMenuItems)
            {
                Console.WriteLine("\t" + m.MenuItemName);
            }
        }
    }
}

The output I got:

enter image description here

I am very much surprised with the output, as I expected the required output as mentioned above.

Vishal
  • 6,238
  • 10
  • 82
  • 158
  • Well you're effectively asking for a transitive relation... how were you expecting that to work? Fundamentally, SQL isn't great at expressing this sort of thing - and nor is LINQ. – Jon Skeet Aug 15 '14 at 12:29
  • @JonSkeet If I use two different tables, then I can get output as mentioned in this video: https://www.youtube.com/watch?v=Da3akpqjaR4&index=21&list=PL6n9fhu94yhWi8K02Eqxp3Xyh_OmQ0Rp6 – Vishal Aug 15 '14 at 12:33
  • @JonSkeet What should I use to get this kind of output? I am asking this because you just mentioned that SQL isn't great at expressing this sort of thing - and nor is LINQ. – Vishal Aug 15 '14 at 12:34
  • 1
    I'm not about to watch a 21 minute video to see what you've done... but I suspect that even with two tables, you won't be able to easily fetch arbitrarily nested groups. You might want to look at http://stackoverflow.com/questions/19148489/return-hierarchy-from-sql-in-lists-in-c-sharp-using-linq though. – Jon Skeet Aug 15 '14 at 12:37
  • 1
    You should probably retrieve all necessary items from the database as a list and create the hierarchical relations application side. – Carvellis Aug 15 '14 at 12:39
  • Have a look at using `hierarchyid`. You can have a computed depth column to manage your indentation. You can also use `IsDescendantOf` to query all children. – Oliver Aug 15 '14 at 12:54
  • possible duplicate of [Hierarchical data in Linq - options and performance](http://stackoverflow.com/questions/202912/hierarchical-data-in-linq-options-and-performance) – Ricardo Souza Aug 15 '14 at 13:19

2 Answers2

2

Your solution can be found here at SO: Hierarchical data in Linq - options and performance.

I won't abstract the code for there are so many answers there that can solve your problem, by I specially like the AsHierarchy() linq extension method as shown on Kyle's answer to that question.

var hierachy = dc.Employees.ToList().AsHierarchy(e => e.EmployeeID, e => e.ReportsTo);

The code for the extension by Stefan Cruysberghs is:

using System;
using System.Collections.Generic;
using System.Linq;

namespace ScipBe.Common.LinqExtensions
{
  // Stefan Cruysberghs, http://www.scip.be, March 2008

  /// <summary>
  /// Hierarchy node class which contains a nested collection of hierarchy nodes
  /// </summary>
  /// <typeparam name="T">Entity</typeparam>
  public class HierarchyNode<T> where T : class
  {
    public T Entity { get; set; }
    public IEnumerable<HierarchyNode<T>> ChildNodes { get; set; }
    public int Depth { get; set; }
  }

  public static class LinqExtensionMethods
  {
    private static System.Collections.Generic.IEnumerable<HierarchyNode<TEntity>> CreateHierarchy<TEntity, TProperty>
      (IEnumerable<TEntity> allItems, TEntity parentItem, 
      Func<TEntity, TProperty> idProperty, Func<TEntity, TProperty> parentIdProperty, int depth) where TEntity : class
    { 
      IEnumerable<TEntity> childs;

      if (parentItem == null)
        childs = allItems.Where(i => parentIdProperty(i).Equals(default(TProperty)));
      else
        childs = allItems.Where(i => parentIdProperty(i).Equals(idProperty(parentItem)));

      if (childs.Count() > 0)
      {
        depth++;

        foreach (var item in childs)
          yield return new HierarchyNode<TEntity>() { Entity = item, ChildNodes = CreateHierarchy<TEntity, TProperty>
            (allItems, item, idProperty, parentIdProperty, depth), Depth = depth };
      }
    }

    /// <summary>
    /// LINQ IEnumerable AsHierachy() extension method
    /// </summary>
    /// <typeparam name="TEntity">Entity class</typeparam>
    /// <typeparam name="TProperty">Property of entity class</typeparam>
    /// <param name="allItems">Flat collection of entities</param>
    /// <param name="idProperty">Reference to Id/Key of entity</param>
    /// <param name="parentIdProperty">Reference to parent Id/Key</param>
    /// <returns>Hierarchical structure of entities</returns>
    public static System.Collections.Generic.IEnumerable<HierarchyNode<TEntity>> AsHierarchy<TEntity, TProperty>
      (this IEnumerable<TEntity> allItems, Func<TEntity, TProperty> idProperty, Func<TEntity, TProperty> parentIdProperty)
      where TEntity : class
    {
      return CreateHierarchy(allItems, default(TEntity), idProperty, parentIdProperty, 0);
    }
  }
}
Community
  • 1
  • 1
Ricardo Souza
  • 16,030
  • 6
  • 37
  • 69
  • Thank you. Both of your answers are very useful but I will use AsHierarchy() linq extension method as the levels are not fixed. To get the output I used a recursive function. But the only problem I get is I don't know : how to increase left margin of the child so that I can see some space before child menuItem's name. So that I can know what is exactly the parent and who is exactly the child. – Vishal Aug 16 '14 at 08:01
  • 1
    You will have the `Depth` property to know the item's level. The items returned by this extension are of type `HierarchyNode` so you will have an `Entity` property to get the real item, a `ChildNodes` property to get it's children and a `Depth` property to get it's depth/level in hierarchy. – Ricardo Souza Aug 18 '14 at 12:46
0

Of course, if your data have only 3 levels you can do it with other GroupJoin() for simplicity:

var hierarchical = menuItems.Where(m => m.ParentID == null)
                                .GroupJoin(menuItems,
                                            m => m.MenuItemID, 
                                            m => m.ParentID,
                                            (parentMenuItems, childMenuItems) => new
                                            {
                                                ParentMenuItems = parentMenuItems,
                                                ChildMenuItems = childMenuItems.GroupJoin(menuItems,
                                                m => m.MenuItemID, 
                                                m => m.ParentID,
                                                (subParentMenuItems, subChildMenuItems) => new
                                                {
                                                    ParentMenuItems = subParentMenuItems,
                                                    ChildMenuItems = subChildMenuItems
                                                })
                                            });

    foreach(var menu in hierarchical)
    {
        Console.WriteLine(menu.ParentMenuItems.MenuItemName);
        foreach(var submenu in menu.ChildMenuItems)
        {
            Console.WriteLine("\t" + submenu.ParentMenuItems.MenuItemName);
            foreach(var subitem in submenu.ChildMenuItems)
            {
                Console.WriteLine("\t\t" + subitem.MenuItemName);
            }
        }
    }
Ricardo Souza
  • 16,030
  • 6
  • 37
  • 69