'Group the similar items in a multiple level hierarchy

I have a list of categories with duplicate key values AppCategoryId. I wanted to create a 3 level hierarchy with that.

Data are in this way:

var appCategoryList = new List<AppCategoryDataModel>() {
    new AppCategoryDataModel() { AppCategoryId = 4844, ParentappCategoryId = null , AppCategoryName = "ItemResearch", ProtocolId = 5164, ProtocolTitle="ABC: Evaluation" },
    new AppCategoryDataModel() { AppCategoryId = 4844, ParentappCategoryId = null, AppCategoryName = "ItemResearch", ProtocolId = null, ProtocolTitle=""},
    new AppCategoryDataModel() { AppCategoryId = 4845, ParentappCategoryId = null, AppCategoryName = "ItemHostpital", ProtocolId = null, ProtocolTitle="" },
    new AppCategoryDataModel() { AppCategoryId = 4845, ParentappCategoryId = null, AppCategoryName = "ItemHostpital", ProtocolId =5162 , ProtocolTitle="ABC: 3/28/20" },
    new AppCategoryDataModel() { AppCategoryId = 4845, ParentappCategoryId = null, AppCategoryName = "ItemHostpital", ProtocolId =5164 , ProtocolTitle="ABC: Evaluation" },
    new AppCategoryDataModel() { AppCategoryId = 4845, ParentappCategoryId = null, AppCategoryName = "ItemHostpital", ProtocolId =5165 , ProtocolTitle="ABC: section", },
    new AppCategoryDataModel() { AppCategoryId = 4845, ParentappCategoryId = null, AppCategoryName = "ItemHostpital", ProtocolId =5192 , ProtocolTitle="ABC: 3/31/20", },
    new AppCategoryDataModel() { AppCategoryId = 4846, ParentappCategoryId = null, AppCategoryName = "Contact information", ProtocolId = null, ProtocolTitle="", },
    new AppCategoryDataModel() { AppCategoryId = 4846, ParentappCategoryId = null, AppCategoryName = "Contact information", ProtocolId =5164 , ProtocolTitle="ABC: Evaluation", },
    new AppCategoryDataModel() { AppCategoryId = 4852, ParentappCategoryId = null, AppCategoryName = "UP", ProtocolId =null , ProtocolTitle="", },
    new AppCategoryDataModel() { AppCategoryId = 4852, ParentappCategoryId = null, AppCategoryName = "UP", ProtocolId = 5164, ProtocolTitle="ABC: Evaluation", },
    new AppCategoryDataModel() { AppCategoryId = 4853, ParentappCategoryId = null, AppCategoryName = "Hospitalist", ProtocolId = null, ProtocolTitle="", },
    new AppCategoryDataModel() { AppCategoryId = 4853, ParentappCategoryId = null, AppCategoryName = "Hospitalist", ProtocolId = 5164, ProtocolTitle="ABC: Evaluation" },
    new AppCategoryDataModel() { AppCategoryId = 4854, ParentappCategoryId = null, AppCategoryName = "COP", ProtocolId =null , ProtocolTitle=""},
    new AppCategoryDataModel() { AppCategoryId = 4854, ParentappCategoryId = null, AppCategoryName = "COP", ProtocolId = 5162, ProtocolTitle="ABC: 3/28/20"},
    new AppCategoryDataModel() { AppCategoryId = 4854, ParentappCategoryId = null, AppCategoryName = "COP", ProtocolId = 5164, ProtocolTitle="ABC: Evaluation" },
    new AppCategoryDataModel() { AppCategoryId = 5023, ParentappCategoryId = null, AppCategoryName = "Call survival guide", ProtocolId = null, ProtocolTitle="" },
    new AppCategoryDataModel() { AppCategoryId = 5023, ParentappCategoryId = null, AppCategoryName = "Call survival guides", ProtocolId =5164 , ProtocolTitle="ABC: Evaluation" },
    new AppCategoryDataModel() { AppCategoryId = 5085, ParentappCategoryId = null, AppCategoryName = "OE", ProtocolId =null, ProtocolTitle="" },
    new AppCategoryDataModel() { AppCategoryId = 5085, ParentappCategoryId = null, AppCategoryName = "OE", ProtocolId =5164 , ProtocolTitle="ABC: Evaluation" },
    new AppCategoryDataModel() { AppCategoryId = 5334, ParentappCategoryId = 5085, AppCategoryName = "mmkk update", ProtocolId =null , ProtocolTitle= null },
    new AppCategoryDataModel() { AppCategoryId = 5336, ParentappCategoryId = 5085, AppCategoryName = "xdgdrg", ProtocolId =null , ProtocolTitle=null },
    new AppCategoryDataModel() { AppCategoryId = 5348, ParentappCategoryId = 5023, AppCategoryName = "test", ProtocolId =null , ProtocolTitle=null },
    new AppCategoryDataModel() { AppCategoryId = 5341, ParentappCategoryId = 5023, AppCategoryName = "New Category Level-1", ProtocolId =null , ProtocolTitle= null },
    new AppCategoryDataModel() { AppCategoryId = 5349, ParentappCategoryId = 5341, AppCategoryName = "New Category Level-2", ProtocolId =null , ProtocolTitle= null },
    new AppCategoryDataModel() { AppCategoryId = 5352, ParentappCategoryId = 5348, AppCategoryName = "New category3", ProtocolId =null , ProtocolTitle= null },

}.OrderBy(ap => ap.AppCategoryName);

AppCategoryDataModel

public class AppCategoryDataModel
{
    public int AppCategoryId { get; set; }
    public int? ParentappCategoryId { get; set; }
    public string AppCategoryName { get; set; }
    public int? ProtocolId { get; set; }
    public string ProtocolTitle { get; set; }
}

Converted the list to lookup:

var lookup = appCategoryList.ToLookup(app => app.AppCategoryId);

Iterated through the list:

var listofCategory = new List<FirstLevelCategory>();

foreach (var item in appCategoryList)
{
    var firstLevelCategory = new FirstLevelCategory();

    if (!item.ParentappCategoryId.HasValue)
    {
        firstLevelCategory.AppCategoryId = item.AppCategoryId;
        firstLevelCategory.AppCategoryName = item.AppCategoryName;

        firstLevelCategory.AppCategoryProtocolData = new List<ProtocolDetails>();
        var protocolDetails = new ProtocolDetails();

        foreach (AppCategoryDataModel appCategory in lookup[item.AppCategoryId])
        {
            if (appCategory.ProtocolId.HasValue)
            {
                protocolDetails.ProtocolId = appCategory.ProtocolId.Value;
                protocolDetails.ProtocolTitle = appCategory.ProtocolTitle;
            }
            firstLevelCategory.AppCategoryProtocolData.Add(protocolDetails);
        }
        continue;
    }

    else
    {

    }

    listofCategory.Add(firstLevelCategory);
}

In the foreach when 'appCategoryList' is iterated first it works fine. But in the next loop due to the repetition of the same appcategoryid there will be a duplicate addition of the same data as much as time the appcateogory id is present.

After all the data has been added I could remove them at the end but it will be another task, what could be done to avoid the addition of the duplicate first?

Alternatively, I tried this way But in this way, only the last item will be added to the list.

foreach (var item in lookup)
{
    var firstLevelCategory = new FirstLevelCategory();

    List<AppCategoryDataModel> categoryList = item.ToList();            foreach (var item in lookup)
{
    var firstLevelCategory = new FirstLevelCategory();

    List<AppCategoryDataModel> categoryList = item.ToList();

    foreach (AppCategoryDataModel appCategory in categoryList)
    {
        if (!appCategory.ParentappCategoryId.HasValue)
        {
            firstLevelCategory.AppCategoryId = appCategory.AppCategoryId;
            firstLevelCategory.AppCategoryName = appCategory.AppCategoryName;
            firstLevelCategory.AppCategoryProtocolData = new List<ProtocolDetails>();
            var protocolDetails = new ProtocolDetails();
            if (appCategory.ProtocolId.HasValue)
            {
                protocolDetails.ProtocolId = appCategory.ProtocolId.Value;
                protocolDetails.ProtocolTitle = appCategory.ProtocolTitle;
            }
            firstLevelCategory.AppCategoryProtocolData.Add(protocolDetails);
        }
        else
        {

        }
    }
    listofCategory.Add(firstLevelCategory);
}

I how can I add the item to the list effectively with a much cheaper approach?

public class FirstLevelCategory
{
    public int AppCategoryId { get; set; }
    public string AppCategoryName { get; set; }
    public List<SecondLevelCategory> SubCategory { get; set; }
    public List<ProtocolDetails> AppCategoryProtocolData { get; set; }
}
public class ProtocolDetails
{
    public int ProtocolId { get; set; }
    public string ProtocolTitle { get; set; }
}

SecondLevelCategory

public class SecondLevelCategory
{
    public int AppCategoryId { get; set; }
    public string AppCategoryName { get; set; }
 
    public IEnumerable<ProtocolDetails> SubCategoryProtocolData { get; set; }
    public List<ThirdLevelCategory> ThirdLevelCategory { get; set; }
}

ThirdLevelCategory

 public class ThirdLevelCategory
 {
    public int AppCategoryId { get; set; }
    public string AppCategoryName { get; set; }
    public IEnumerable<ProtocolDetails> ThirdLevelCategoryProtocolData { get; set; }
 }

Expected Output are something similar (output for the first category only)

ItemResearch
    ABC: Evaluation
ItemHostpital
    ABC: 3/28/20
    ABC: Evaluation
    ABC: section
    ABC: 3/31/20
Contact information
    ABC: Evaluation
UP
    ABC: Evaluation

Edit: More data are added that have parentscategoryid, and class for first, second and third level are added.



Solution 1:[1]

Using dictionaries makes your life easier:

        Dictionary<int, FirstLevelCategory> dictCategories = new Dictionary<int, FirstLevelCategory>();
        foreach (AppCategoryDataModel category in appCategoryList) 
        {
            if (!dictCategories.ContainsKey(category.AppCategoryId))
            {
                dictCategories.Add(category.AppCategoryId, new FirstLevelCategory()
                {
                    AppCategoryId = category.AppCategoryId,
                    AppCategoryName = category.AppCategoryName,
                    AppCategoryProtocolData = category.ProtocolId != null ? new List<ProtocolDetails>() { new ProtocolDetails() {
                        ProtocolId = (int)category.ProtocolId,
                        ProtocolTitle = category.ProtocolTitle
                    } } : new List<ProtocolDetails>()
                });
            }
            else 
            {
                if (category.ProtocolId != null)
                dictCategories[category.AppCategoryId].AppCategoryProtocolData.Add(new ProtocolDetails()
                {
                    ProtocolId = (int)category.ProtocolId,
                    ProtocolTitle = category.ProtocolTitle
                });
            }
        }
        var test = dictCategories.Values.ToList();

I didn't check for duplicates in protocol, as for yours code seems there is no need but if it's needed you can do it the same way

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 J.Salas