'preventing the item to add to the dictionary

In addition to this question:

I want to create a hierarchical list in a 3 level category.

My data are in the following ways:

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 = 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 = 5099, ParentappCategoryId = null, AppCategoryName = "OEM", ProtocolId =null , ProtocolTitle=null },

    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; }
}

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>(),

            SubCategory = category.ParentappCategoryId != null
            ? GetSubCategoryList(appCategoryList, category.AppCategoryId)
            : new List<SecondLevelCategory>()
        });
    }
    else
    {
        if (category.ProtocolId != null)
            dictCategories[category.AppCategoryId].AppCategoryProtocolData.Add(new ProtocolDetails()
            {
                ProtocolId = (int)category.ProtocolId,
                ProtocolTitle = category.ProtocolTitle
            });
    }
}
foreach (var key in dictCategories.Keys.ToList())
{
    dictCategories[key].CategoryProtocolCount = dictCategories[key].AppCategoryProtocolData.Count();
}

FirstLevelCategory

public class FirstLevelCategory
{
    public int AppCategoryId { get; set; }
    public string AppCategoryName { get; set; }
    public bool IsExpand { get; set; }
    public bool IsExpandAll { get; set; }
    public bool IsCategoryHierarchyExpanded { get; set; }
    //count of AppCategoryProtocolData 
    public int CategoryProtocolCount { 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; }
}

My problem here is the item where ParentappCategoryId != null should come as a sub-category, but that has also been added in the first level category. I am not able to filter that properly.

How should I prevent the item that has ParentAppCategory to be added in the first.

SecondLevelCategory

public class SecondLevelCategory
{
    public int AppCategoryId { get; set; }
    public string AppCategoryName { get; set; }
    public bool? ToggleSubCategory { get; set; }
    public bool IsExpand { get; set; }
    public int subcategoryProtocolCount { 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 bool? ToggleThirdLevelCategory { get; set; }
    public int? ThirdLevelCategoryProtocolCount { get; set; }
    public IEnumerable<ProtocolDetails> ThirdLevelCategoryProtocolData { get; set; }
 }

Expected Result:

   ItemResearch
        ABC: Evaluation
    ItemHostpital
        ABC: 3/28/20
        ABC: Evaluation
        ABC: section
        ABC: 3/31/20
    Contact information
        ABC: Evaluation
    UP
        ABC: Evaluation
    OE
        ABC: Evaluation
            mmkk update --subcategory
            xdgdrg      --subcategory
    Call survival guides
        ABC: Evaluation --details
            test  --subcategory
                New category3 --thirdcategory
            New Category Level-1 --subcategory
                New Category Level-2 -- thirdcategory
 


Solution 1:[1]

My problem here is the item where ParentappCategoryId != null should come as a sub-category, but that has also been added in the first level category. I am not able to filter that properly.

I interpret this as I want only items with ParentappCategoryId == null in the first level of my dictionary.

If that is the case, it should be possible to solve by filtering the app category list before iterating through it to add the first-level items to your dictionary.

Here is an example of how that can be achieved using .Where() from the System.Linq namespace:

Replace

foreach (AppCategoryDataModel category in appCategoryList)

with

//using System.Linq;

var firstLevelItems = appCategoryList.Where(app => app.ParentappCategoryId == null);

foreach (AppCategoryDataModel category in firstLevelItems)

Note: A separate variable is not needed; I would mainly consider using it for readability.

You might as well implement it as follows:

//using System.Linq;

foreach (AppCategoryDataModel category in appCategoryList.Where(app => app.ParentappCategoryId == null))

Also -- I am not sure if this part causes a problem for you, but the logic looks strange to me:

SubCategory = category.ParentappCategoryId != null
    ? GetSubCategoryList(appCategoryList, category.AppCategoryId)
    : new List<SecondLevelCategory>()

I interpret the code as:

Populate category's SubCategory only if category has a parent

To me, given your condition for the first-level items of not having a parent, this seems to imply that none of the first-level items should have any sub categories. I find this to be contradictive. Please correct me if I'm wrong.

If I am right, I'd suggest implementing GetSubCategoryList() so that it returns an empty list if none of the items in appCategoryList has a ParentappCategoryId == category.AppCategoryId.

If you have that, you can simply go with

SubCategory = GetSubCategoryList(appCategoryList, category.AppCategoryId)

Update: Possible implementation of GetSubCategoryList (GetSubCategories)

You mention in your comment that your current implementation of GetSubCategoryList (and GetThirdLevelCategoryList) pretty much repeats the implementation of your main foreach loop.

To my understanding, this indicates that the classes FirstLevelCategory and SecondLevelCategory (and ThirdLevelCategory) could be replaced by a single class: AppCategory.

public class AppCategory
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int ProtocolCount { get; set; }
    public List<AppCategory> SubCategories { get; set; }
    public List<ProtocolDetails> ProtocolData { get; set; }
}

Furthermore, the logic for populating AppCategoryProtocolData could be simplified by grouping all the app categories that share (AppCategory)Id and creating a list of ProtocolDetails based on that grouping.

From here, we can make use of recursion to get the sub categories of each category.

private static List<AppCategory> GetSubCategories(
    List<AppCategoryDataModel> appCategories,
    int? parentAppCategoryId)
{
    // return empty list if no sub categories exist
    if (!appCategories.Any(ac => ac.ParentAppCategoryId == parentAppCategoryId))
    {
        return new();
    }
    
    List<AppCategory> subCategories = appCategories
        .Where(ac => ac.ParentAppCategoryId == parentAppCategoryId)
        // Simplify the logic of populating the ProtocolData content
        .GroupBy(ac => ac.AppCategoryId)
        .Select(gr => new AppCategory
            {
                Id = gr.Key,
                Name = gr.First().AppCategoryName,
                ProtocolData = gr
                    .Where(ac => ac.ProtocolId != null)
                    .Select(ac => new ProtocolDetails
                        {
                            Id = ac.ProtocolId.Value,
                            Title = ac.ProtocolTitle 
                        })
                    .ToList()
            })
        .ToList();
    
    foreach (var subCat in subCategories)
    {
        // Make recursive method call to populate _this_ category's sub categories
        subCat.SubCategories = GetSubCategories(appCategories, subCat.Id);
        subCat.ProtocolCount = subCat.ProtocolData.Count;
    }
    
    return subCategories;
}

As it turns out, all the first-level items in your dictionary may in fact be considered as being sub categories of a (non-existing) category with Id == null. We can take advantage of this observation by finding our first-level items using our implementation of GetSubCategories.

The main code block may be reduced to:

Dictionary<int, AppCategory> dictCategories = new();

var firstLevelCategories = GetSubCategories(appCategoryList, null);

foreach (var category in firstLevelCategories)
{
    dictCategories.Add(category.Id, category);
}

The resulting hierarchy, using your example input data, looks as follows:

Call survival guide
        ABC: Evaluation
                New Category Level-1
                        New Category Level-2
                test
                        New category3
Contact information
        ABC: Evaluation
ItemHostpital
        ABC: 3/28/20
        ABC: Evaluation
        ABC: section
        ABC: 3/31/20
ItemResearch
        ABC: Evaluation
OE
        ABC: Evaluation
                mmkk update
                xdgdrg
OEM
UP
        ABC: Evaluation

Note that this result includes OEM, which is not included in your expected result. If you only want first-level items in your dictionary that contain ProtocolData, you could e.g. filter as follows:

foreach (var category in firstLevelCategories.Where(c => c.ProtocolCount > 0))
{
    dictCategories.Add(category.Id, category);
}

Example fiddle here.

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