2

Following the last answer : Recursive method to convert flat collection to hierarchal collection?

I want to use the same method CreateTree but with another object than Hierarchy: ItemNode:

public class ItemNode
{
    public string Id { get; set; }
    public Item Item { get; set; }
    public ICollection<ItemNode> Children { get; set; }
}

and the definition of Item:

public class Item
{
    public string ID { get; set; }
    public string Name { get; set; }
    public int Level { get; set; }
    public string ParentId { get; set; }
}

And here the CreateTree methods with the ItemNode:

    static List<ItemNode> CreateTreeItems(IEnumerable<ItemNode> nodes)
    {
        Dictionary<string,ItemNode> idToNode = nodes.ToDictionary(n => n.Id, n => n);
        List<ItemNode> roots = new List<ItemNode>();
        ItemNode root = null;

        foreach (var n in nodes)
        {
            if (n.Item.ParentId == null)
            {
                if (root != null)
                {
                    roots.Add(root);
                }
                root = n;
                continue;
            }

            ItemNode parent = idToNode[n.Item.ParentId];
            //if (!idToNode.TryGetValue(n.Item.ParentId, out parent))
            //{
            //  //Parent doesn't exist, orphaned entry
            //}

            parent?.Children.Add(n);

            // RETURNS FALSE WHEREAS IN THE ORIGINAL METHOD IT RETURNS TRUE
            var test = Object.ReferenceEquals(parent, root);
            Debug.WriteLine(test);
        }


        if (root == null)
        {
            //There was no root element
        }

        roots.Add(root);
        return roots;
    }

It does not work because parent and root does not reference the same object (whereas in the original method, it does). I guess it was linked to the fact that I have added an Item property to the ItemNode class. But I don't know how to fix it.

Thank you !

Florian
  • 4,507
  • 10
  • 53
  • 73

1 Answers1

1

Why do you expect the root and parent node references to be equal? Every root node might be a parent node but not every parent node is a root node.

There might be a situations where the references are equal but it very depends on a sort order of the nodes collection. Actually when root node is placed earlier than the 1st level child node you will see the situation where the references are equal.

I suppose your problem is elsewhere, for instance it might not work if the Nodes collection doesn't have a root node at all.

Here's the example, try it here https://dotnetfiddle.net/4r52xP

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

public class Hierarchy
{
    public Hierarchy(string iD, string name, int level, string parentID, string topParent)
    {
        ID = iD;
        Name = name;
        Level = level;
        ParentID = parentID;
        Children = new HashSet<Hierarchy>();
    }
    public string ID { get; set; }
    public string Name{ get; set; }
    public int Level { get; set; }
    public string ParentID { get; set; }
    public ICollection<Hierarchy> Children { get; set; }
}

public class Program
{
    static Hierarchy CreateTree(IEnumerable<Hierarchy> Nodes)
    {
        var idToNode = Nodes.ToDictionary(n => n.ID, n => n);

        Hierarchy root = null;
        foreach (var n in Nodes)
        {
            if (n.ParentID == null)
            {
                if (root != null)
                {
                    //there are multiple roots in the data
                }
                root = n;
                continue;
            }

            Hierarchy parent = null;
            if (!idToNode.TryGetValue(n.ParentID, out parent))
            {
                //Parent doesn't exist, orphaned entry
            }

            parent.Children.Add(n);

            Console.WriteLine("ReferenceEquals: {0}", Object.ReferenceEquals(parent, root));
        }

        if (root == null)
        {
            //There was no root element
        }
        return root;
    }

    public static void Main()
    {
        Console.WriteLine("Test #1");

        List<Hierarchy> l = new List<Hierarchy>();
        l.Add(new Hierarchy("295152","name1",1,null, null)); // <-root node at the top of the list
        l.Add(new Hierarchy("12345","child1",2,"295152", null));
        l.Add(new Hierarchy("54321","child2",2,"295152", null));
        l.Add(new Hierarchy("44444","child1a",3,"12345", null));
        l.Add(new Hierarchy("33333","child1b",3,"12345", null));
        l.Add(new Hierarchy("22222","child2a",3,"54321", null));
        l.Add(new Hierarchy("22221","child2b",3,"54321", null));
        l.Add(new Hierarchy("22002","child2c",3,"54321", null));
        l.Add(new Hierarchy("20001","child2a2",4,"22222", null));
        l.Add(new Hierarchy("20101","child2b2",4,"22222", null));       

        CreateTree(l);

        Console.WriteLine("\nTest #2");

        l = new List<Hierarchy>();
        l.Add(new Hierarchy("12345","child1",2,"295152", null));
        l.Add(new Hierarchy("54321","child2",2,"295152", null));
        l.Add(new Hierarchy("44444","child1a",3,"12345", null));
        l.Add(new Hierarchy("33333","child1b",3,"12345", null));
        l.Add(new Hierarchy("22222","child2a",3,"54321", null));
        l.Add(new Hierarchy("22221","child2b",3,"54321", null));
        l.Add(new Hierarchy("22002","child2c",3,"54321", null));
        l.Add(new Hierarchy("20001","child2a2",4,"22222", null));
        l.Add(new Hierarchy("20101","child2b2",4,"22222", null));
        l.Add(new Hierarchy("295152","name1",1,null, null)); // <-root node at the bottom of the list
        CreateTree(l);
    }
}
  • Yes it works fine with the Hierarchy class. But when I want to replace the Hierarchy by ItemNode, it does not work anymore. I suspect it's because of parent and root variable does not reference the same object whereas they reference the same object with hierarchy object : https://dotnetfiddle.net/JnX5ow – Florian Feb 18 '19 at 14:18
  • 1
    @Florian Ok, now I see. You've become a victim of a LINQ laziness. To see how often it creates the `ItemNode` object replace the `Select` block to the next: `var rawItems = GetItems().Select(c => { Console.WriteLine("Creating the Node: {0}", c.ID); return new ItemNode { Id = c.ID, Item = c, Children = new List() }; });`. In your example `idToNode` dictionary and `nodes` collection contain different objects. – Sergey Shevchenko Feb 18 '19 at 14:47
  • 1
    @Florian and in general your issue may be solved by simply changing the call to `var test = CreateTreeItems(rawItems.ToList());` – Sergey Shevchenko Feb 18 '19 at 14:48
  • Yes you're right ! thank you !!! When I use your block, I see the line "Creating the Node: 295152" and the line Creating the Node: 12345" twice. Wheres I use the .ToList() extension method. Those duplicate lines does not appear anymore. But do you know why ? BTW, thank you again ! – Florian Feb 18 '19 at 15:06
  • 1
    That's all because of LINQ deferred execution. Take a closer look here https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/introduction-to-linq-queries#query-execution . When you use `ToList()` - you're passing a complete collection into the `CreateTreeItems` method. However when you don't - the method recives the chain of commands that may produce a collection. Both `ToDictionary` and `foreach` execute that chain of command and end up with two different collections. – Sergey Shevchenko Feb 18 '19 at 15:11
  • I will check your link. Thx ! – Florian Feb 18 '19 at 15:23