4

I want to filter an IEnumerable object by a specific property of whatever object it is collecting. I want the option to filter by one or more property value but how many values (and what values) to filter by is only known at runtime.

Ok, so to give an example, the collected objects could be the following struct:

public struct Person { public string Name { get; set; } public string Profession{ get; set; } } 

This struct could then be used by the following list, which I have populated with some arbitrary values:

List<Person> people= new List<Person>; people.Add(new Person(){Name = "Mickey", Profession="Tinker"}; people.Add(new Person(){Name = "Donald", Profession="Tailor"}; people.Add(new Person(){Name = "Goofy", Profession="Soldier"}; people.Add(new Person(){Name = "Pluto", Profession="Spy"}; 

This is then put into an IEnumerable (all of them are transferred to it first)

var wantedPeople = from n in this.people select n; 

So say a user was only interested in the "Tailor" and "Spy" professions, and via some sort of gui trickery the following collection was created:

List<string> wantedProfessions = new List<string>(); wantedProfessions.Add("Tailor"); wantedProfessions.Add("Spy"); 

Now what Linq statement can I use to filer my wantedPeople so it only includes the tailor and spy entries? I know I could use a where clause but I don't know how to tailor it to get what I want (and doing the following is not what I want as it only works with the wantedProfessions collection above (e.g. this collection will change at runtime):

wantedPeople = from n in wantedPeople where n.Profession == wantedProffessions[0] || n.Profession == wantedProffessions[1] select n; 

2 Answers 2

7

If you want to check any wanted profession from given list:

wantedPeople = from n in wantedPeople where wantedProffessions.Contains(n.Profession) select n; 

Or you can build query with lambda syntax by applying filters one by one:

var query = people.AsEnumerable(); if (!String.IsNullOrEmpty(name)) query = query.Where(p => p.Name == name); if (wantedProfessions.Any()) query = query.Where(p => wantedProfessions.Contains(p.Profession)); 

If you wanted to create more complex filters, like some name, and several professions, you can use Specification pattern. Specification can be defined by this simple interface:

public interface ISpecification<T> { bool Satisfied(T entity); } 

It just checks whether given entity (person) satisfies specification. Specification also look very simple:

public class PersonNameSpecification : ISpecification<Person> { private string _name; public PersonNameSpecification(string name) { _name = name; } public bool Satisfied(Person person) { return person.Name == _name; } } 

Profession specification:

public class PersonProfessionSpecification : ISpecification<Person> { private string[] _professions; public PersonProfessionSpecification(params string[] professions) { _professions = professions; } public bool Satisfied(Person person) { return _professions.Contains(person.Profession); } } 

You can create specifications which implement boolean logic, like OrSpecification or AndSpecification:

public class AndSpecification<T> : ISpecification<T> { private ISpecification<T> _specA; private ISpecification<T> _specB; public AndSpecification(ISpecification<T> specA, ISpecification<T> specB) { _specA = specA; _specB = specB; } public bool Satisfied(T entity) { return _specA.Satisfied(entity) && _specB.Satisfied(entity); } } public static class SpecificationExtensions { public static ISpecification<T> And<T>( this ISpecification<T> specA, ISpecification<T> specB) { return new AndSpecification<T>(specA, specB); } } 

Now you can create complex specification which describes people you want to get:

var professionSpec = new PersonProfessionSpecification("Tailor", "Spy"); var nameSpec = new PersonNameSpecification("Pluto"); var spec = professionSpec.And(nameSpec); 

And get required people:

var result = people.Where(spec.Satisfied); 
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks Sergey and aplogies for the long delay.
0

Sergey B's Solution is the correct one for your example.

Assuming that you weren't using a collection that had the Contains() method, you could also do the following:

var wantedPeople = from n in people from p in wantedProffessions where n.Profession.Equals(p) select n; 

1 Comment

Actually I would go with join in that case - it creates lookup internally which is more efficient than enumerating both collections O(n*m)

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.