Essential LINQ Query Expression Clauses and Operators
The group Clause
The group clause organizes data into sequences based on a specified key value. Keys can be of any data type. The following example demonstrates creating groups where the key is derived from the first character of a region's designation, producing a collection of grouped elements with character-based keys.
var regionalGroups =
from region in regions
group region by region.Title[0];
For comprehensive coverage of grouping operations, refer to the group clause documentation.
The select Clause
The select clause produces sequences of various types. A straightforward select clause generates a sequence where each element matches the type found in the data source. In this case, the data source contains Region objects. The orderby clause reorders elements while select produces the reordered sequence.
IEnumerable<Region> orderedQuery =
from region in regions
orderby region.Size
select region;
The select clause also transforms source data into sequences of different types—this transformation is called projection. The example below uses select to project anonymous type sequences containing only specific fields from the original elements. New instances are created using object initializers.
var projection =
from region in regions
select new
{
Label = region.Name,
Pop = region.Residents
};
The var keyword becomes necessary here because the query produces an anonymous type.
For extensive examples of transforming source data with the select clause, consult the select clause documentation.
Using the "into" Continuation
The group keyword combined with into creates a temporary identifier that stores query results. The into keyword proves useful when additional operations must follow grouping or selection. In the following example, regions gets grouped by million-unit ranges based on population. After creating these groups, subsequent clauses filter certain groups and sort them in ascending order. The regionCluster identifier represents the continuation enabling these operations.
// clusterQuery is an IEnumerable<IGrouping<int, Region>>
var clusterQuery =
from region in regions
let bucket = (int)region.Residents / 1_000
group region by bucket into regionCluster
where regionCluster.Key >= 20
orderby regionCluster.Key
select regionCluster;
// cluster is an IGrouping<int, Region>
foreach (var cluster in clusterQuery)
{
Console.WriteLine(cluster.Key);
foreach (var region in cluster)
{
Console.WriteLine(region.Name + ":" + region.Residents);
}
}
Complete details on the into continuation appear in the into documentation.
Filtering, Sorting, and Joining
Between the opening from clause and closing select or group clause, all other clauses (where, join, orderby, from, let) are optional. Any of these optional clauses may appear zero or multiple times with in the query body.
The where Clause
The where clause filters source elements based on one or more predicate expressions. The following example applies a where clause containing one predicate with two conditions.
IEnumerable<Town> filteredTowns =
from town in towns
where town.Residents is < 15_000_000 and > 10_000_000
select town;
Refer to the where clause documentation for further details.
The orderby Clause
The orderby clause arranges results in ascending or descending order. Secondary sorting criteria can also be specified. The example below performs primary sorting by the Size property of Region objects, followed by secondary sorting using the Residents property in descending order.
IEnumerable<Region> orderedRegions =
from region in regions
orderby region.Size, region.Residents descending
select region;
The ascending keyword is optional and represents the default sorting direction when no order is specified. Consult the orderby clause documentation for additional information.
The join Clause
The join clause associates and combines elements from one data source with elements in another based on equality comparisons of specified keys. In LINQ, join operations work across sequences of potentially different element types. After joining two sequences, a select or group statement must specify which elements populate the output sequence. Anonymous types can merge properties from each matched elemant set into a single new type for the output. The example below joins categories objects whose cat property matches an entry in a Category string array with corresponding products. Elements with no matching category get excluded. The select statement projects a new type combining properties from both cat and prod.
var categoryMatch =
from cat in categories
join prod in products on cat equals prod.Category
select new
{
Category = cat,
Name = prod.Name
};
Group joins are possible by storing join results in a temporary variable and applying the into keyword. Further information available in the join clause documentation.
The let Clause
The let clause stores expression results—such as method return values—into a new range variable. In the example below, the range variable firstName captures the first element of the string array returned by Split.
string[] names = ["Svetlana Omelchenko", "Claire O'Donnell", "Sven Mortensen", "Cesar Garcia"];
IEnumerable<string> firstNameQuery =
from name in names
let firstName = name.Split(' ')[0]
select firstName;
foreach (var s in firstNameQuery)
{
Console.Write(s + " ");
}
//Output: Svetlana Claire Sven Cesar
Consult the let clause documentation for complete details.
Subqueries Within Query Expressions
A query clause itself may contain another query expression, referred to as a subquery. Each subquery begins with its own from clause, which doesn't necessarily reference the same data source as the initial from clause. The following example illustrates a subquery within a select statement used to retrieve results from a grouping operation.
var groupMaximum =
from student in students
group student by student.Year into studentCluster
select new
{
Level = studentCluster.Key,
TopScore = (
from secondary in studentCluster
select secondary.ExamScores.Average()
).Max()
};
Additional information on executing subqueries against grouped data appears in the subqueries on grouped data documentation.