Th4t Guy
Th4t Guy

Reputation: 1492

Create a nested list of items from objects with a parent reference

I am using C# and I have a list of items that have a nullable parent ID property.

I need to convert this to a list of items that have a list of their children and keep going down generations until there are no items left.

My existing class

public class Item
{
    public int Id { get; set; }
    public int? ParentId { get; set; }
}

My first thoughts...

Create a class

public class ItemWithChildren
{
    public Item Item { get; set; }
    public List<ItemWithChildren> Children { get; set; }
}  

Now I need some way to get a List<ItemWithChildren> that has all the top level Item objects and their children into the Children property.

Note that the nesting is not a set number of levels.

I was hoping there was an elegant LINQ query that would work. So far I just have this...

var itemsWithChildren = items.Select(a => new ItemWithChildren{ Item = a });

Upvotes: 6

Views: 11820

Answers (4)

Mick
Mick

Reputation: 6864

I think you need to do it in two steps... I've tested and it definitely works...

        var items = new List<Item>();
        items.Add(new Item { Id = 1, ParentId = null });
        items.Add(new Item { Id = 2, ParentId = 1 });
        items.Add(new Item { Id = 3, ParentId = 1 });
        items.Add(new Item { Id = 4, ParentId = 3 });
        items.Add(new Item { Id = 5, ParentId = 3 });

        var itemsWithChildren = items.Select(a =>
            new ItemWithChildren { Item = a }).ToList();

        itemsWithChildren.ForEach(a =>
                a.Children = itemsWithChildren.Where(b => 
                    b.Item.ParentId == a.Item.Id).ToList());

        var root = itemsWithChildren.Single(a => !a.Item.ParentId.HasValue);

        Console.WriteLine(root.Item.Id);
        Console.WriteLine(root.Children.Count);
        Console.WriteLine(root.Children[0].Children.Count);
        Console.WriteLine(root.Children[1].Children.Count);

Output...

1 2 0 2

Upvotes: 0

Charles Graham
Charles Graham

Reputation: 1157

It is more readable to not use pure Linq for this task, but a mixture of Linq and looping.

Given the following container:

 class Node
 {
    public int Id { get; set; }
    public int? ParentId { get; set; }

    public List<Node> Children { get; set; }
 }

Then you can make the tree with the following code.

    var nodes = new List<Node>
    {
        new Node{ Id = 1 },
        new Node{ Id = 2 },
        new Node{ Id = 3, ParentId = 1 },
        new Node{ Id = 4, ParentId = 1 },
        new Node{ Id = 5, ParentId = 3 }
    };

    foreach (var item in nodes)
    {
        item.Children = nodes.Where(x => x.ParentId.HasValue && x.ParentId == item.Id).ToList();
    }

    var tree = nodes.Where(x => !x.ParentId.HasValue).ToList();

This will handle any level of depth and return a proper tree.


Given the following method to print the tree:

private void PrintTree(IEnumerable<Node> nodes, int indent = 0)
{
    foreach(var root in nodes)
    {
        Console.WriteLine(string.Format("{0}{1}", new String('-', indent), root.Id));
        PrintTree(root.Children, indent + 1);
    }
}

The output of this call is:

1
-3
--5
-4
2

If however you want to use pure Linq for this, you can do the following, however to me it is harder to read:

       var tree = nodes.Select(item =>
        {
            item.Children = nodes.Where(child => child.ParentId.HasValue && child.ParentId == item.Id).ToList();
            return item;
        })
        .Where(item => !item.ParentId.HasValue)
        .ToList();

Upvotes: 5

Selman Gen&#231;
Selman Gen&#231;

Reputation: 101721

This might help ?

var itemsWithChildren = items.Select(a => new ItemWithChildren{ 
                                         Item = a,
                                         Children = items.Where(b => b.ParentId==a.Id)
                                                         .ToList()
                                    });

Upvotes: 2

Sarbanjeet
Sarbanjeet

Reputation: 253

Then update model to achieve it as

  public string Name { get; set; }
    [DisplayName("Parent Category")]
    public virtual Guid? CategoryUID { get; set; }
    public virtual ICollection<Category> Categories { get; set; }

Upvotes: 0

Related Questions