0

I have a Model called Category which has a recursive (parent child) relationship as follows:

public class Category: ITreeNode<Category>
    {
        public byte Id { get; set; }
        public byte? ParentId { get; set; }
        public string Name { get; set; }

        public Category Parent { get; set; }
        public IList<Category> Children { get; set; }

    }

I want to generate a dropdownlist that is heirachaly grouped based on the parent child relations where I can also select the parent group as follows - basically I want to indent the children from the parent:

 <select name="Category" id="Category">
       <option value="0">All</option>
       <option value="1">Cars</option>
       <option value="2">--Toyota</option>
       <option value="3">--Nissan</option>
       <option value="4">Fruits</option>
       <option value="5">--Apples</option>
       <option value="6">--Oranges</option>
    </select>

My Table data is as follows:

 Id  | ParentId  | Name
 ----------------------
 1   |   null    |  Cars
 2   |   1       |  Toyota
 3   |   1       |  Nissan
 4   |   null    |  Fruits
 5   |   4       |  Apples
 6   |   4       |  Oranges

Currently I have the following LINQ query but it simply generates a normal dropdown ordered by id.

    public IEnumerable<SelectListItem> GetCategoriesSelectList()
    {
        var categories = new List<SelectListItem>
            {
                new SelectListItem() {Value = "0", Text = "All" }

            }.Concat(_context.Category.Select(x => new SelectListItem
            {
                Value = x.Id.ToString(),
                Text = x.Name
            }).OrderBy(x => x.Value).ToList());

        return categories
    }

How can I amend the LINQ query to get them correctly grouped and indented for when it's rendered using a Html.DropDownListFor.

I've tried to amend my original select list to achieve some kind of tree as follows but I'm stuck on the EnumerateNodes method. The original below is for printing out a ul list which I pulled from the following site http://www.codeproject.com/Articles/23949/Building-Trees-from-Lists-in-NET. How do I iterate through it and return each item, if its a child then append -- to the name and add to my select list?

   public IEnumerable<SelectListItem> GetCategoriesSelectList()
    {
        IList<Category> listOfNodes = GetListOfNodes();
        IList<Category> topLevelCategories = TreeHelper.ConvertTOForest(listOfNodes);

       var cats = new List<SelectListItem> 
       { 
            new SelectListItem { Value = "0", Text = "All"} 
       };

       foreach(var category in topLevelCategories) {
            var catName = EnumerateNodes(category);
            cats.Add(new SelectListItem { Value = category.Id.ToString(), Text = catName });
       }
        return cats;
    }

    private List<Category> GetListOfNodes()
   {

    List<Category> sourceCategories = _context.Category.ToList();
    List<Category> categories = new List<Category>();
    foreach (Category sourceCategory in sourceCategories)
    {
      Category s = new Category();
      s.Id = sourceCategory.Id;
      s.Name = sourceCategory.Name;
      if (sourceCategory.ParentId != null)
      {
          s.Parent = new Category();
          s.Parent.Id = (int)sourceCategory.ParentId;
      }
      categories.Add(s);
    }
    return categories;
  }

 private static string EnumerateNodes(Category parent)
{
   if (category.Children.Count > 0) {
      Response.Write("<ul>");
      foreach(Category child in category.Children) {
          EnumerateNodes(child);
      }
      Response.Write("</ul>");
   }
   Response.Write("</li>");
}
adam78
  • 9,668
  • 24
  • 96
  • 207
  • Where do you get stuck exactly? If you want to know how to create dropdownlist, you might want to look at [Best programming practice of using DropDownList in ASP.Net MVC](http://stackoverflow.com/a/37819577/296861). – Win Jul 21 '16 at 18:15
  • @win I know how to create the lists, is how to write the LINQ to get them grouped and indented. – adam78 Jul 21 '16 at 18:26
  • 1
    Possible duplicate of [Build tree type list by recursively checking parent-child relationship C#](http://stackoverflow.com/questions/15867478/build-tree-type-list-by-recursively-checking-parent-child-relationship-c-sharp) – Win Jul 21 '16 at 18:32
  • @Win its not a duplicate. My question is slightly different. Its related to building a dropdownlist. – adam78 Jul 21 '16 at 18:44

2 Answers2

0

You can first select the parent categories (where ParentId is null) and then for each parent category, select its child categories.

public IEnumerable<SelectListItem> GetCategoriesSelectList()
{
    var categories = _context.Category.ToList();
    // Initialise list and add first "All" item
    List<SelectListItem> options = new List<SelectListItem>
    {
        new SelectListItem(){ Value = "0", Text = "All" }
    };
    // Get the top level parents
    var parents = categories.Where(x => x.ParentId == null);
    foreach (var parent in parents)
    {
        // Add SelectListItem for the parent
        options.Add(new SelectListItem()
        {
            Value = parent.Id.ToString(),
            Text = parent.Name
        });
        // Get the child items associated with the parent
        var children = categories.Where(x => x.ParentId == parent.Id);
        // Add SelectListItem for each child
        foreach (var child in children)
        {
            options.Add(new SelectListItem()
            { 
                Value = child.Id.ToString(),
                Text = string.Format("--{0}",child.Name)
            });
        }
    }
    return options;
}
  • I get error `There is already an open DataReader associated with this Command which must be closed first.` on the following line `foreach (var child in children)`. To fix I've ammended `var categories = _context.Category;` to `var categories = _context.Category.ToList()`. Theres also a typo on the line `var children = products.Where(x => x.ParentId == parent.Id);` This should be `var children = categories.Where(x => x.ParentId == parent.Id);` If you amend your answer I can mark it as solution. Cheers. – adam78 Jul 22 '16 at 07:40
0

You'll need to call the categories recursively so that you can get infinite level nesting if you want it. The above code mentioned only works for 2 levels of nesting, so you will only have 2 levels here: Parents and Children. Create the function in such a way that it takes the categoryId as a parameter and the recursive function takes the categoryId as the parentId so you can go deeper into your tree by another level, but eventually you can have that new category be a parent to another category.