Overview

A query expression is a query expressed in query syntax. All query expressions are converted to Standard Query Operator method calls at compile time.

Query expressions:

  • Must begin with a from clause
  • Optionally contain where, orderby, join, let, or other from clauses
  • Must end with a select or group clause

Query Syntax

Query syntax looks like this:

int[] numbers = [ 5, 10, 8, 3, 6, 12 ];

IEnumerable<int> numQuery1 = // 'numQuery1' is the query variable
    from num in numbers // 'num' is the range variable; it is typed based on the type of elements in 'numbers'
    where num % 2 == 0
    orderby num
    select num;

foreach (int i in numQuery1) // The iteration variable (i) iterates over the query variable (numQuery1), thereby executing it
{
    Console.Write(i + " ");
}

Any query that can be expressed in query syntax can also be expressed using method syntax.

  • Use query syntax whenever possible, method syntax whenever necessary. There is no performance difference between the two.
  • Some query operations like Count or Max have no query syntax equivalent and require method syntax.
  • Method syntax can be combined with query syntax.

Method syntax

Method syntax is made possible by extension methods on IEnumerable<T> (and IQueryable<T>) that implement the standard query operators.

Method syntax looks like this:

IEnumerable<int> numQuery2 = numbers.Where(num => num % 2 == 0)
                                    .OrderBy(n => n);

The Where extension method implements the where standard query operator.
The OrderBy extension method implements the orderby standard query operator.

📝 Note

Some LINQ providers implement their own standard query operators and extension methods for types other than IEnumerable<T>.

Mixed Query and Method Syntax

When mixing the two syntaxes, instead of this:

int numCount1 = (
  from num in numbers1
  where num < 3 || num > 7
  select num).Count();

Do this (create a new variable to store the method call result):

IEnumerable<int> numbersQuery =
  from num in numbers1
  where num < 3 || num > 7
  select num;

int numCount2 = numbersQuery.Count(); // This method call forces the query expression to execute immediately.

Subqueries

When a query clause contains a query expression, this is called a subquery. Each subquery starts with its own from clause. These are common when retrieving the results of a grouping operation:

var queryGroupMax =
    from student in students
    group student by student.Year into studentGroup
    select new
    {
        Level = studentGroup.Key,
        HighestScore = (
            from student2 in studentGroup
            select student2.ExamScores.Average()
        ).Max()
    };

Query use cases

Filter the data

IEnumerable<int> filteringQuery =
    from score in scores
    where score > 80 or < 90
    select score;

Sort/group a subset of the data

IEnumerable<int> highScoresQuery =
    from score in scores
    where score > 80 // Get a subset of the data…
    orderby score descending // …then sort/group it.
    select score;

Transform a subset of the data

IEnumerable<string> highScoresQuery2 =
    from score in scores
    where score > 80 // Get a subset of the data…
    orderby score descending
    select $"The score is {score}"; // …then transform it (int –> string).

Get a singleton value about the data

int highScoreCount = ( // Because highScoreCount stores a result, it is *not* a query variable.
    from score in scores
    where score > 80 // Get a subset of the data…
    select score).Count(); // …then get a singleton value about the data.

Handle null in query expressions

If a source collection is null or contains an element whose value is null, and the query expression does not guard against these cases, a NullReferenceException is thrown when the query is executed.

To guard:

if (categories is not null) // explicitly check the source collection for null
{
    var query1 =
        from c in categories
        where c != null // where clause filters out null elements in the 'categories' sequence
        join p in products on c.ID equals p?.CategoryID
        select new
        {
            Category = c.Name,
            Name = p.Name
        };
}