LINQ: query syntax and method syntax

4 min read
On trails:CSLINQstep 1/4

Language-Integrated Query (LINQ) provides a powerful, unified way to query data from many sources, such as in-memory collections, databases (via EF Core) and XML documents. It uses one consistent, SQL-like syntax, directly in C#.

LINQ lets you write declarative queries. You focus on what data you need, not how to retrieve it. That improves readability and maintainability.

Two forms

LINQ queries can be written in two equivalent forms.

Query syntax

Query syntax is declarative and resembles SQL, with keywords like from, where, select, orderby, join and group.

C#
int[] numbers = { 5, 10, 8, 3, 6, 12 };IEnumerable<int> evenOrderedNumbersQuery =    from num in numbers    where num % 2 == 0    orderby num    select num;

Method syntax

Method syntax, also called fluent syntax, uses a sequence of extension method calls (the standard query operators) on IEnumerable<T> or IQueryable<T> objects. It is often combined with lambda expressionsLambda expressionsA short way to write a method inline: an anonymous function.Opens in a new tab for predicates and projections.

C#
int[] numbers = { 5, 10, 8, 3, 6, 12 };IEnumerable<int> evenOrderedNumbersMethod = numbers                                             .Where(num => num % 2 == 0)                                             .OrderBy(n => n);

Equivalence and choice

Both syntaxes are semantically identical. The C# compiler translates query syntax into method syntax calls at compile time. In these two examples, the pink where clause becomes the pink .Where(...) call, and the orange orderby clause becomes .OrderBy(...).

Choosing between them is often a matter of personal preference, or of readability for a particular query. Some LINQ operators are only available in method syntax, for example Count(), ToList() and FirstOrDefault().

What the compiler turns it into

Query syntax is only a way of writing. The compiler rewrites it into the method calls before anything runs, so the compiled code contains the extension methods and none of the SQL-like keywords. The from, where and orderby keywords exist only in your source.

C#
// What you write (query syntax)var query =    from num in numbers    where num % 2 == 0    orderby num    select num; // What the compiler turns it intovar query = numbers    .Where(num => num % 2 == 0)    .OrderBy(num => num);

The closing select num doesn’t change anything, so the compiler leaves it out. The rewrite goes by method name, so any type that has Where and Select methods works with query syntax, not only IEnumerable<T>. With an IQueryable<T>, both forms hand the provider the same expression tree (see LINQ providersLINQ providers: IQueryable and IEnumerableWhere a LINQ query actually runs depends on its provider, and on IQueryable versus IEnumerable.Opens in a new tab).

Since the two forms compile to the same thing, choose by readability. Queries with join, let or several from clauses often read better as query syntax. Everything else, and the operators that only exist as methods, such as Count() and ToList(), reads better as method syntax.

Composability

LINQ queries typically return an IEnumerable<T> (LINQ to Objects) or an IQueryable<T> (LINQ to Entities and SQL), as the providers noteLINQ providers: IQueryable and IEnumerableWhere a LINQ query actually runs depends on its provider, and on IQueryable versus IEnumerable.Opens in a new tab covers. That lets you chain query operations and build more complex queries from simpler parts. It’s a powerful way to write dynamic, readable data retrieval logic.

Lambdas are the delegates

LINQ methods are ordinary methods that take a delegate parameter. So passing a lambda is exactly what you did when you passed a method into a filter (see delegatesDelegatesA type-safe function pointer, so a method can be passed around like any other value.Opens in a new tab), using Microsoft’s pre-built versions. Where expects a Func<T, bool>: give it an item, and it tells you true or false. Internally it loops through the items and calls your lambda on each, keeping the ones that return true. Select expects a Func<T, TResult>: give it an item, and it gives you back something new. Each method returns another sequence, which is why they chain.

C#
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6 }; var evens = numbers.Where(n => n % 2 == 0); // n => ... is the Func<int, bool>var doubled = numbers.Select(n => n * 2);   // n => ... is the Func<int, int> var result = numbers    .Where(n => n % 2 == 0) // keep evens    .Select(n => n * n);    // square them foreach (var r in result)    Console.WriteLine(r);   // 4, 16, 36

The lambda has to match the shape of the delegate the method expects:

LINQ method Delegate expected What the lambda means
Where(x => ...) Func<T, bool> Keep it or discard it?
Select(x => ...) Func<T, TResult> Transform it into what?
OrderBy(x => ...) Func<T, TKey> Sort by what value?
Any(x => ...) Func<T, bool> Does at least one match?
Sum(x => ...) Func<T, int>, or decimal and so on What value to add up?

For example, people.OrderBy(p => p.Age) is a Func<Person, int>: sort using this value. The lambda syntax itself is in the lambdas noteLambda expressionsA short way to write a method inline: an anonymous function.Opens in a new tab.

Standard query operators

LINQ provides a rich set of standard query operators, implemented as extension methodsExtension methodsAdd methods to a type you can't change, without touching its source or subclassing it.Opens in a new tab. Common ones are:

  • Select (projection)
  • Where (filtering)
  • OrderBy and ThenBy (sorting)
  • GroupBy (grouping)
  • Join (joining)
  • Aggregate (custom aggregation)
  • Skip and Take (paging)
  • First, FirstOrDefault, Single, SingleOrDefault
  • Any, All, Count, Sum, Min, Max, Average, and more

Custom extension methods

Because the standard query operators are extension methods, you can write your own domain-specific, LINQ-like extension methods. They can encapsulate common query logic, and make business rules more declarative and reusable within the application’s data access layer.


Sources