0

For a project I want to create recursive an object (menu) hierarchy using C#. I read and tried a lot from the internet but I cannot fix it fast. To clarify my problem, I have a simple database table created.

CREATE TABLE Page
(
    Id INT NOT NULL PRIMARY KEY IDENTITY,
    ParentId INT DEFAULT 0,
    MenuTitle VARCHAR(255) NOT NULL
)

INSERT INTO Page (ParentId, MenuTitle) VALUES
(0, 'Parent 1'),
(1, 'Child 1'),
(1, 'Child 2'),
(3, 'ChildChild 1'),
(3, 'ChildChild 2'),
(3, 'ChildChild 3'),
(0, 'Parent 2'),
(0, 'Parent 3'),
(0, 'Parent 4')

*ParentId 0 is a main navigation item

This is the model.

class PageItem
{
    public int Id { get; set; }
    public int ParentId { get; set; }
    public string MenuText { get; set; }
    List<PageItem> Childs { get; set; }
}

To load the data from the database you can use this method.

public List<PageItem> GetPageItems()
        {
            List<PageItem> pageItems = new List<PageItem>();
            SqlConnection conn = new SqlConnection(" * YOUR CONNECTIONSTRING *");
            SqlCommand cmd = new SqlCommand("SELECT Id, ParentId, MenuTitle FROM Page", conn);
            conn.Open();
            SqlDataReader rdr = cmd.ExecuteReader();
            while (rdr.Read())
            {
                pageItems.Add(new PageItem()
                {
                    Id = Convert.ToInt32(rdr["Id"]),
                    ParentId = Convert.ToInt32(rdr["ParentId"]),
                    MenuText = rdr["MenuTitle"].ToString()
                });
            }
            rdr.Close();
            conn.Close();
            return pageItems;
        }

I want that the model is filled recursively so it looks like this.

- Parent 1
-- Child 1
-- Child 2
--- ChildChild 1
--- ChildChild 2
--- ChildChild 3
- Parent 2
- Parent 3
- Parent 4

Does anyone know how I could realize this?

Thanks a lot.

Jordy

4 Answers 4

1

Replace your read loop with:

var allItems = new List<PageItem>();
while (rdr.Read())
{
   var item = new PageItem()
              {
                  Id = Convert.ToInt32(rdr["Id"]),
                  ParentId = Convert.ToInt32(rdr["ParentId"]),
                  MenuText = rdr["MenuTitle"].ToString()
              });
   allItems.Add(item);
   var parent = allItems.Where(pi => pi.Id == item.ParentId).SingleOrDefault();
   if (parent == null)
   {
      pageItems.Add(item);
   }
   else
   {
      if (parent.Childs == null)
         parent.Childs = new List<PageItem>();
      parent.Childs.Add(item);
   }
}
Sign up to request clarification or add additional context in comments.

2 Comments

Thank you. The last line gives a null reference exception.
I see 7 parents and there are 4 parents (see main post). Level 3 in the object is missing. I think this solution only works for two levels deep, while there may be unlimited.
1

For bigger datasets using a Dictionary would give better Performance. It will only have to iterate through all probably childs one time and the lookup for the parent in the Dictionary is close to O(1) This code relies on properly sanitized values in the database.

Dictionary<int, PageItem> Items = new Dictionary<int, PageItem>();
while (rdr.Read())
{
    var item = new PageItem()
    {
        Id = Convert.ToInt32(rdr["Id"]),
        ParentId = Convert.ToInt32(rdr["ParentId"]),
        MenuText = rdr["MenuTitle"].ToString(),
        Childs = new List<PageItem>()
    };
    Items[item.Id] = item;
}
foreach (var pair in Items)
{
    PageItem item = pair.Value;
    if (item.ParentId == 0)
        continue;

    Items[item.ParentId].Childs.Add(item);
}

Comments

0

Before returning pageItems, iterate each item and populate the Childs collection with items that have it as a parent...

using System.Linq;
// ...

pageItems.ForEach(p => 
{
    p.Childs = pagesItems.Where(c => c.ParentId == p.Id).ToList();
});

return pageItems;

Comments

0

You don't need any code. You can use a common table expression like so

;with cte as 
(
    select convert(varchar(20),'-') as caption, *, convert(varchar(10),id) as o 
    from Page 
    where ParentId=0
    union all
    select convert(varchar(20),caption + '-'), page.id, page.parentid, page.menutitle, convert(varchar(10),o + convert(varchar(10),page.id) )
    from cte
        inner join page on cte.Id = page.ParentId    
)    
select caption+' ' + MenuTitle from cte
order by o

2 Comments

It must be in code, because i want a generic method to use in C# - and that's better readable.
Then you should state "I want it to be more complex than is necessary"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.