LINQ filter by attribute nested in 3 lists

Viewed 348

I am kind of new to working with LINQ and ran into a problem which I can't seem to solve.

So I basically need to filter on an attribute of an object that is nested inside three lists. The Data (as a JSON) looks as follows:

  [
   {
      "userName":"John",
      "resourceTypeUserVms":[
         {
            "name":"Administration",
            "description":null,
            "resourceUserVms":[
               {
                  "policyUserVm":{
                     "policyName":"admin",
                     "priority":0,
                     "metaHash":"abc123",
                     "policySetName":"Admin",
                     "policySetDescription":""
                  },
                  "accessType":"Administration",
                  "accessDescription":null,
                  "metaData":"{\"Name\":\"Admin\"}"
               },
               {
                  "policyUserVm":{
                     "policyName":"test",
                     "priority":0,
                     "metaHash":"abc123",
                     "policySetName":"Test",
                     "policySetDescription":""
                  },
                  "accessType":"Administration",
                  "accessDescription":null,
                  "metaData":"{\"Name\":\"test123\"}"
               }
            ]
         },
         {
            "name":"Database",
            "description":null,
            "resourceUserVms":[
               {
                  "policyUserVm":{
                     "policyName":"read_x",
                     "priority":0,
                     "metaHash":"def456",
                     "policySetName":"Test",
                     "policySetDescription":""
                  },
                  "accessType":"read",
                  "accessDescription":null,
                  "metaData":"{\"Server\":\"srv\",\"Name\":\"test\"}"
               }
            ]
         },
         {
            "name":"Configuration",
            "description":null,
            "resourceUserVms":[
               {
                  "policyUserVm":{
                     "policyName":"readc",
                     "priority":0,
                     "metaHash":"ghi789",
                     "policySetName":"Configurations",
                     "policySetDescription":""
                  },
                  "accessType":"read",
                  "accessDescription":null,
                  "metaData":"{\"Name\":\"ServiceName\"}"
               }
            ]
         }
      ]
   }
]

So I basically have a User with x amount of ResourceTypesUserVms and those ResourceTypes have x ResourceUserVms.

I need to filter on the attribute "metaData" inside of the ResourceUserVms. So if for example enter "Admin" into the filter bar, I want to get something like this in return:

[
   {
      "userName":"John",
      "resourceTypeUserVms":[
         {
            "name":"Administration",
            "description":null,
            "resourceUserVms":[
               {
                  "policyUserVm":{
                     "policyName":"admin",
                     "priority":0,
                     "metaHash":"abc123",
                     "policySetName":"Admin",
                     "policySetDescription":""
                  },
                  "accessType":"Administration",
                  "accessDescription":null,
                  "metaData":"{\"Name\":\"Admin\"}"
               }
            ]
         }
      ]
   }
]

I have found some solutions online suggesting I do something like this:

filteredUsers = users
        .Where(x => x.ResourceTypeUserVms
        .Any(x => x.ResourceUserVms
        .Any(x => x.MetaData.ToLower().Contains(value))))
        .ToList();

However, this does not yield the result I want.

I got the expected result by doing this (unclean code ahead):

foreach(var user in users)
        {
            foreach(var resourceType in user.ResourceTypeUserVms)
            {
                foreach(var resource in resourceType.ResourceUserVms)
                {
                    if (resource.MetaData.ToLower().Contains(value))
                    {
                        filteredUsersTemp.Add(
                            new UserUserVm
                            {
                                UserName = user.UserName,
                                ResourceTypeUserVms = new List<ResourceTypeUserVm>(
                                    new ResourceTypeUserVm[] {
                                        new ResourceTypeUserVm
                                        {
                                            Description = resourceType.Description,
                                            Name = resourceType.Name,
                                            ResourceUserVms = new List<ResourceUserVm>(
                                                new ResourceUserVm[]{ resource }
                                                )
                                        }
                                    })
                            }
                            );
                    }
                }
            }
        }

I would really appreciate if someone could help me figure out a nice, clean way of doing this using LINQ!

2 Answers

I guess, you need to filter ResourceUserVms by MetaData as well.

So, firstly, get all users that have ResourceTypeUserVms that have ResourceUserVms that have MetaData (==value). You actually did it.

Then you can get rid of MetaData-useless ResourceUserVms.

   filteredUsers = users
            .Where(x => x.ResourceTypeUserVms
            .Any(x => x.ResourceUserVms
            .Any(x => x.MetaData.ToLower().Contains(value))))
            .ToList();

filteredUsers = filteredUsers.Select(x=>{

    x.ResourceTypeUserVms = x.ResourceTypeUserVms.Select(y=> {

       y.ResourceUserVms = y.ResourceUserVms.Where(s=>s.MetaData.ToLower().Contains(value)))).ToList();

       return y;

    }).ToList();

return x;

}).ToList();
from user in users
from resourceType in user.ResourceTypeUserVms
from resource in resourceType.ResourceUserVms
where resource.MetaData.ToLower().Trim().Contains(value)

Also, it is recommended to use Any() instead of Contains().

Related