mbob
mbob

Reputation: 630

LINQ filter list in list by a specific term

I know it has been discussed a lot, but, being a beginner in LINQ I don't manage to filter this situation:

I have a list of Customers containing a list of Projects

list to filter:

List<Customer> Customers {get; set;}

this is Customer class:

class Customer
{
    public string Name {get; set;}
    public List<Project> Projects {get; set;}
}

this is Project class:

class Project
{
    public string Name {get; set;}
}

All I want is to find all customers containing ONLY the projects that have a certain name (my app will filter by the project's name).

As for now I have this:

Customers
    .Where(c => c.Projects.Any(p => p.Name.ToLowerInvariant().Contains(lowerCaseFilter)));

but it doesn't help, because it will return all the customers containing ALL the projects not only the one I have searched.

For example, if I have a list of 15 customers with an unknown number of projects in each of them, I want to display only those customers and only those projects in every customers that meet the searched term.

I think I am missing something here...

Upvotes: 1

Views: 1151

Answers (3)

Bal&#225;zs
Bal&#225;zs

Reputation: 2929

I am not sure what you mean by

it will return all the customers containing ALL the projects not only the one I have searched.

I can interpret that in two ways, please clarify:

  • You want to return all customers that have at least one project with the specified name, and exclude the nonmatching projects from the final results (a)
  • You want to return only those customers that have projects matching the filter and no other projects (b) (In other words if any of the projects doesn't match the filter, then that customer should be left out regardless of whether or not it has a matching project).

Solution for (a):

Customers
  .Where(c => c.Projects.Any(p => p.Name.ToLowerInvariant().Contains(lowerCaseFilter)))
  .Select(c => new Customer
  {
    // set all other properties
    Projects = c.Projects
      .Where(p => p.Name.ToLowerInvariant().Contains(lowerCaseFiler))
      .ToList()
  });

What you do here is simply filter the Projects collection of the result by the desired value.

Solution for (b):

Customers
  .Where(c => c.Projects.All(p => p.Name.ToLowerInvariant().Contains(lowerCaseFilter)))

This simply filters the customers collection for those items that only contain projects which match the desired filter.

Upvotes: 0

mm8
mm8

Reputation: 169340

Something like this:

var customers = Customers
            .Select(c => new Customer() { Name = c.Name, Projects = c.Projects == null ? new List<Project>() : c.Projects.Where(p => !string.IsNullOrEmpty(p.Name) && p.Name.ToLower().Contains(lowerCaseFilter)).ToList() })
            .Where(c => c.Projects.Any())
            .ToList();

You will have to create new Customer objects that only contain the projects that you want.

Upvotes: 0

Tim Schmelter
Tim Schmelter

Reputation: 460228

You have to recreate the customers and the projects if you want to modify the list:

var searchedCustomers = Customers
    .Select(c => new { 
        Customer = c,
        FilteredProjects = c.Projects
            .Where(p => string.Equals(p.Name, nameFilter, StringComparison.InvariantCultureIgnoreCase))
            .ToList()
    })
    .Where(x => x.FilteredProjects.Any())
    .Select(x => new Customer{ 
        Name = x.Customer.Name,  
        Projects = x.FilteredProjects 
    }); 

I've used String.Equals with StringComparison.InvariantCultureIgnoreCase to avoid creating lower case strings and to avoid some localization issues like the turkish i problem. So you also don't need the lowerCaseFilter with this approach so i've named it nameFilter.

Upvotes: 3

Related Questions