Eric
Eric

Reputation: 8088

Remove Parents that are children in the same Object using Linq

I have a Menu built from a stored procedure that returns a folder hierarchy. The procedure returns FolderID, Name, and ParentID. In my Menu repository I use ForEach() on my list of Folders to add every SubFolder associated with a Parent Folder.

It looks like this:

folders = await _dbcontext.MenuFolders.FromSql("EXEC UserMenuFolders @p0", UserID)
    .Select(x => new MenuFolder 
        {
            FolderID = x.FolderID, 
            FolderName = x.FolderName, 
            SortOrder = x.SortOrder, 
            SubofID = x.SubofID 
        })      
    .ToListAsync();

folders.ForEach(x => x.SubFolders = folders
    .Where(y => y.SubofID == x.FolderID)
    .OrderBy(y => y.SortOrder)
    .ToList());

This gives me an object that looks a bit like:

 "Folder 1": {
    "Sub Folder 1": [
        "SubFolder 1.1",
        "SubFolder 1.2"
    ],
    "Sub Folder 2": [
        "SubFolder 2.1",
        "SubFolder 2.2"
    ]
}, 
"Folder 2": {
    //Other sub Folders
 },
"SubFolder 1":{},
"SubFolder 1.1":{},
"SubFolder 1.2": {}

You can see that the SubFolders are in there twice because the initial query gets All Folders. How can I remove from the list the folders that are subfolders? Also, I'd be interested in seeing if there more efficient methods of creating this menu structure, but keep in mind I had to use the Stored Procedures because the Authorization Logic was already built there.

Here is my feeble attempt that did nothing:

folders.RemoveAll(x => x.SubFolders.Any(c=> c.FolderID == x.FolderID));

And Below is my MenuFolders Entity:

public int FolderID { get; set; }
public string FolderName { get; set; }        
public int ParentID { get; set; }
[ForeignKey("ParentID")]
public List<MenuFolder> SubFolders { get; set; }
public Int16 SortOrder { get; set; }

Upvotes: 1

Views: 601

Answers (2)

Federico Dipuma
Federico Dipuma

Reputation: 18305

Just use recursion. Assuming root folders (folders without a parent) have ParentId == 0, you may use something like this:

public static List<MenuFolder> ToTree(IEnumerable<MenuFolder> flatList, int parentId = 0)
{
    var tree = flatList
        .Where(m => m.ParentID == parentId)
        .ToList();

    tree.ForEach(t => t.SubFolders = ToTree(flatList, t.FolderID));

    return tree;
}

And just return:

var foldersTree = ToTree(folders);

Note: this will create a tree-like structure of any depth (not just two levels like in your example)

Upvotes: 1

Travis J
Travis J

Reputation: 82337

You were close with your attempt. Basically, what you want to remove are children folders. That means that if a folder's parent id exists as any folder's id, it must be a child, and as a result you can safely remove it.

folders.RemoveAll(x => folders.Any(f => f.FolderID == x.ParentID));

Upvotes: 2

Related Questions