For those who are looking for a more generic approach that is very quick and easy to use. I have made some generic interfaces and extension methods that will quickly sort and return hierarchical data.
public interface IHierarchicalData<TData>
{
public IHierarchicalData<TData>? Parent { get; set; }
public IList<IHierarchicalData<TData>> Children { get; set; }
public TData? Data { get; set; }
}
public class DefaultHierarchy<TData> : IHierarchicalData<TData>
{
public IHierarchicalData<TData>? Parent { get; set; }
public IList<IHierarchicalData<TData>> Children { get; set; } = new List<IHierarchicalData<TData>>();
public TData? Data { get; set; }
}
public static class HierarchyExtensions
{
public static IEnumerable<THierarchyModel> CreateHierarchy<THierarchyModel, TData, TId>(this IEnumerable<TData> flatList, Func<TData, TId> idSelector, Func<TData, TId?> parentIdSelector)
where THierarchyModel : IHierarchicalData<TData>, new()
{
var lookup = flatList.Select(f => new THierarchyModel { Data = f })
.Where(item => item.Data is not null)
.ToDictionary(h => idSelector(h.Data));
foreach (var item in lookup.Values)
{
var parentId = parentIdSelector(item.Data);
if (parentId is null || !lookup.TryGetValue(parentId, out var parent))
{
yield return item;
continue;
}
parent.Children.Add(item);
item.Parent = parent;
}
}
public static IEnumerable<IHierarchicalData<TData>> CreateHierarchy<TData, TId>(this IEnumerable<TData> flatList, Func<TData, TId> idSelector, Func<TData, TId?> parentIdSelector)
{
return flatList.CreateHierarchy<DefaultHierarchy<TData>,TData, TId>(idSelector, parentIdSelector);
}
}
Usage is very simple. All you have to do is pass in the func to get the parentId and Id of the item. Then you can traverse the hierarchy and get the information you need by accessing the "Data" property.
private IEnumerable<IHierarchicalData<TestHierarchy>> hierarchyData
=> testHierarchy.CreateHierarchy(t => t.Id, t => t.ParentId);
private class TestHierarchy
{
public int Id { get; set; }
public int? ParentId { get; set; }
public string Name { get; set; }
}
private List<TestHierarchy> testHierarchy = new()
{
new() { Id = 1, ParentId = null, Name = "Top Level 1" },
new() { Id = 2, ParentId = 1, Name = "Top Level 1.1" },
new() { Id = 3, ParentId = 1, Name = "Top Level 1.2" },
new() { Id = 12, ParentId = 3, Name = "Top Level 1.2.1" },
new() { Id = 10, ParentId = 1, Name = "Top Level 1.3" },
new() { Id = 11, ParentId = 1, Name = "Top Level 1.4" },
new() { Id = 4, ParentId = null, Name = "Top Level 2" },
new() { Id = 5, ParentId = 4, Name = "Top Level 2.1" },
new() { Id = 6, ParentId = 4, Name = "Top Level 2.2" },
new() { Id = 7, ParentId = 4, Name = "Top Level 2.3" },
new() { Id = 13, ParentId = 7, Name = "Top Level 2.3.1" },
new() { Id = 14, ParentId = 13, Name = "Top Level 2.3.1.1" },
new() { Id = 8, ParentId = 4, Name = "Top Level 2.4" },
new() { Id = 9, ParentId = null, Name = "Top Level 3" },
};
