In this article, we’ll focus on the use cases of the LINQ Where Method in C#.
The LINQ Where Method offers a powerful tool that can manipulate data from various sources ranging from collections of objects in memory to relational database tables and XML files according to a specific criterion. It offers a simple, intuitive syntax similar to SQL, that makes writing queries on collections easier, simpler, maintainable, and more efficient.
Let’s begin!
The Signatures of the LINQ Where Extension Method
The Where()
query method has three different signatures, each accepting a collection as input and returning a subset of the original collection.
IEnumerable<T> Collections Filtering
Firstly, .NET provides a Where()
method signature allowing filtering of an IEnumerable<T>
collection using a predicate. The predicate takes a TSource
element and returns a boolean indicating whether or not the element should be included in the filtered result:
public static IEnumerable<TSource> Where<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate)
Let’s use this Where()
method on a collection of integers:
int[] randomNumbers = [1, 5, 23, 11, 7, 2, 9, 8, 3, 6, 4, 20, 15, 0, 18, 13, 10, 33]; var evenNumbers = randomNumbers.Where(x => x % 2 == 0).OrderBy(x => x).ToList();
We apply the filter operator here to retain only the even numbers. If we print the resulting list to the console we see:
0, 2, 4, 6, 8, 10, 18, 20
When working with an in-memory enumerable object, Where()
acts as an extension method of the IEnumerable<T>
interface.
IEnumerable<T> Collection Indexed Filtering
Secondly, another Where()
overload provides access to the index of the current element in the predicate as well as to the element itself:
public static IEnumerable<TSource> Where<TSource>( this IEnumerable<TSource> source, Func<TSource, int, bool> predicate)
It is important to note that as with all C# collections, the index of an enumerable starts at 0, not 1.
Here, we only want to keep the numbers at odd indices:
var oddNumbers = Enumerable.Range(50, 10).Where((_, index) => index % 2 != 0).ToList();
The result is:
51, 53, 55, 57, 59
Queryable<T> Collections Filtering
The third overload of the Where()
method allows filtering IQueryable<T>
collections, which represent queries on a data source that may not be in memory, typically like databases. However, in this case, a lambda expression tree forms the predicate:
public static IQueryable<TSource> Where<TSource>( this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
First of all, to illustrate this overload of the Where
operator, we’ll use a database of people. In this model, a person may have zero, one or multiple pets and live in a single address. Additionally, we use Entity Framework Core to access the database. In the context class, we add data to our entities using the OnModelCreating()
method. Implementation details are available in the source code.
Now, let’s query the database to retrieve people who were born in 1974 or later:
IQueryable<Person> queryResult = context.People.Where(p => p.BirthDate.Year >= 1974); return queryResult.ToList();
Here, we obtain two persons corresponding to our request:
JACKSON Colleen RICHARD Dan
We can continuously add operations like OrderBy()
or additional Where()
clauses to the same IQueryable
query. Each operation builds a new expression tree that represents the cumulative query.
The actual SQL query is not generated and sent to the database until we enumerate the IQueryable
object, such as by calling ToList()
. This ensures that all modifications are included in a single, efficient SQL query, resulting in optimized database access and only returning the filtered results.
Examples of the LINQ Where Method in Action
Let’s explore some use cases of the LINQ Where()
method.
Before exploring the examples, we need to remember that the Where()
method, like most other LINQ operations, is not executed on construction, but rather on enumeration. This occurs, for instance, when calling ToList()
or looping through a foreach
. This behavior is known as deferred execution.
Multiple LINQ Where Method Filtering
Firstly, it is possible to chain Where()
clauses to refine the filtered data with each iteration.
Let’s gather information on people born before 1974 but who also reside in the city of Nancy, France:
IQueryable<Person> result = context.People .Include(person => person.Address) .Where(s => s.BirthDate.Year < 1974) .Where(s => s.Address.City.Equals("NANCY")); return result.ToList();
According to our database data, only one person meets these criteria:
LAWRENCE Dennis
Nested LINQ Where Operators
We also have the option to include an instruction using another Where
operator in the predicate of one Where
operator:
IQueryable<Person> PeopleWithAustralianShepherd = context.People .Include(p => p.Pets) .Where(person => person.Pets.Where(pet => pet.Breed.Contains("Australian")) .Any(pet => pet.Name.Equals("Naïa"))); return PeopleWithAustralianShepherd.ToList();
Here, the query returns:
RICHARD Dan
LINQ Where Method With an Expression as Parameter
Finally, let’s rewrite the previous LINQ query, this time using an expression as a parameter of the Where
operator:
Expression<Func<Person, bool>> HasAustralianShepherds = p => p.Pets.Any(pet => pet.Breed.Equals("Australian Shepherd") && pet.Name.Equals("Naïa")); var filteredResult = context.People.Where(HasAustralianShepherds); return filteredResult.ToList();
Again, our query returns :
RICHARD Dan
Conclusion
In this article, we learned how to filter a sequence of elements according to a boolean condition with the LINQ Where Method. We looked at chaining the Where operator with other LINQ operators to create more complex queries. We also looked at the signatures of the Where operator and explored some use cases.