Edit

LINQ queries

Tip

This article is part of the Fundamentals section for developers who already know at least one programming language and are learning C#. If you're new to programming, start with the Get started tutorials first. For more information about providers, operators, and advanced scenarios, see Language Integrated Query (LINQ).

Coming from another language? LINQ query syntax reads like a SQL-style query written inside C#. LINQ method syntax reads like chained collection operations in JavaScript, Java streams, or Python pipelines. Both forms describe the same query.

Language Integrated Query (LINQ) is the C# feature set for querying data with C# syntax. Many LINQ operators use deferred execution: they describe the result first and read the data later, when your code asks for the results. A query describes which data to read and how to shape the result. A query reads from a data source. A data source can be an in-memory collection, such as an array or List<T>, or an external source, such as a database or XML, exposed through a LINQ provider. A LINQ provider is a library that connects LINQ syntax to a specific kind of data source. This article uses in-memory collections for its examples. A sequence is an ordered set of elements represented by IEnumerable<T>. For more information about provider-based queries, see Language Integrated Query (LINQ).

LINQ query expression syntax

The examples in this article read in-memory collections such as arrays and List<T>. A query usually has three parts: specify the data source, describe the result, and enumerate the source to produce the result. To enumerate a sequence means to read its elements one at a time, often with foreach.

string[] names = ["Ana", "Ben", "Cleo", "Dara"];

IEnumerable<string> query =
    from name in names
    where name.Length >= 4
    orderby name
    select name;

foreach (string name in query)
{
    Console.WriteLine(name);
}
// This example produces the following output:
//
//    Cleo
//    Dara
//

The query expression starts with from to name the data source and range variable. The where clause keeps only matching elements, orderby sorts them, and select shapes the result.

LINQ method syntax

Query syntax uses clauses such as from, where, orderby, and select. Method syntax calls LINQ methods directly. For in-memory sequences, the standard LINQ methods are the built-in Enumerable methods for filtering, projection, sorting, grouping, and related operations.

string[] names = ["Ana", "Ben", "Cleo", "Dara"];

IEnumerable<string> query = names
    .Where(name => name.Length >= 4)
    .OrderBy(name => name)
    .Select(name => name);

foreach (string name in query)
{
    Console.WriteLine(name);
}
// This example produces the following output:
//
//    Cleo
//    Dara
//

Method syntax is also called fluent syntax because each call returns a result that the next call can use. Many queries can use either form. Use the form that makes the query easiest to read.

Query syntax often reads well when the query has several clauses. The let clause gives a name to an intermediate value before the query filters, sorts, and selects the final result:

(string Area, int Priority)[] workItems =
[
    ("docs", 2),
    ("tests", 1),
    ("deploy", 4),
    ("api", 1)
];

IEnumerable<string> nextItems =
    from item in workItems
    let label = $"{item.Area}: P{item.Priority}"
    where item.Priority <= 2
    orderby item.Priority, item.Area
    select label;

foreach (string item in nextItems)
{
    Console.WriteLine(item);
}
// This example produces the following output:
//
//    api: P1
//    tests: P1
//    docs: P2
//

Method syntax often reads well for short operations. It's necessary for methods that don't have query-syntax keyword. For example, Count returns one value directly:

List<string> workItems = ["design", "docs", "deploy", "review"];

int count = workItems.Count(item => item.StartsWith('d'));

Console.WriteLine($"Starts with d: {count}"); // => Starts with d: 3

Lambda expressions and LINQ

A lambda expression is an anonymous function that you can pass as an argument. LINQ method syntax commonly uses lambda expressions to say what each operator should do with each element.

string[] names = ["Ana", "Ben", "Cleo"];

IEnumerable<string> shortNames = names
    .Where(name => name.Length == 3)
    .Select(name => name.ToUpperInvariant());

foreach (string name in shortNames)
{
    Console.WriteLine(name);
}
// This example produces the following output:
//
//    ANA
//    BEN
//

In name => name.Length == 3, name is the input element and name.Length == 3 is the Boolean expression that decides whether the element belongs in the result. For more information about lambda expressions and delegate types, see Lambda expressions, delegates, and events in the fundamentals.

Query-syntax clauses use lambda expressions too. Clauses such as where, orderby, and select compile to method calls that take lambda expressions. The range variable becomes the lambda parameter, and the clause expression becomes the lambda body. Query syntax is a concise way to write those same lambdas:

string[] workItems = ["docs", "test", "deploy"];

IEnumerable<string> querySyntax =
    from item in workItems
    where item.Length == 4
    select item;

IEnumerable<string> methodSyntax =
    workItems.Where(item => item.Length == 4);

foreach (string item in querySyntax)
{
    Console.WriteLine($"Result: {item}");
}

foreach (string item in methodSyntax)
{
    Console.WriteLine($"Result: {item}");
}
// This example produces the following output:
//
//    Result: docs
//    Result: test
//    Result: docs
//    Result: test
//

Common LINQ methods

The most common LINQ methods cover the everyday steps of a query: filter data, shape results, sort, group, and summarize. These methods are in the System.Linq namespace and work with sequences such as IEnumerable<T>.

  • Where keeps only the elements that match a condition.
  • Select transforms each element into a new value. C# calls this operation a projection.
  • OrderBy sorts elements.
  • GroupBy creates groups of elements that share a key.
  • Aggregation methods such as Sum, Count, and Aggregate produce a single value from all elements in the data source.

Note

If you know the traditional functional-programming terms, Where is a filter, Select is a map (C# calls it a projection), and aggregation methods such as Aggregate, Sum, and Count are a reduce.

Dictionary<string, int> priorities = new()
{
    ["Docs"] = 2,
    ["Code"] = 1,
    ["Test"] = 3,
    ["Deploy"] = 4
};

IEnumerable<string> plannedWork = priorities
    .Where(workItem => workItem.Value <= 3)
    .OrderBy(workItem => workItem.Value)
    .Select(workItem => $"{workItem.Key}: {workItem.Value}");

foreach (string item in plannedWork)
{
    Console.WriteLine(item);
}
// This example produces the following output:
//
//    Code: 1
//    Docs: 2
//    Test: 3
//

A projection creates a result value from each input element. In the previous example, the projection creates strings that combine a work item name and its priority.

Use GroupBy when the result should contain groups of elements that share a key. Each group is represented by IGrouping<TKey,TElement>, which exposes the group key and the elements in that group.

string[] labels = ["api", "auth", "docs", "deploy"];

IEnumerable<IGrouping<char, string>> groups = labels.GroupBy(label => label[0]);

foreach (IGrouping<char, string> group in groups)
{
    Console.WriteLine($"{group.Key}: {string.Join(", ", group)}");
}
// This example produces the following output:
//
//    a: api, auth
//    d: docs, deploy
//

Grouping is useful for summaries, reports, and menus. For more information about joins, nested groupings, and provider-specific behavior, see Language Integrated Query (LINQ).

Run a query

Many LINQ operators use deferred execution. Deferred execution means operators that return a sequence, such as Where, Select, and OrderBy, don't run when you define them. They build the recipe for producing results. A foreach loop is one way to run that recipe:

List<string> workItems = ["design", "docs"];

IEnumerable<string> query = workItems.Where(item => item.StartsWith('d'));

workItems.Add("deploy");

// The query runs here, when foreach asks for the results.
foreach (string item in query)
{
    Console.WriteLine(item);
}
// This example produces the following output:
//
//    design
//    docs
//    deploy
//

Other operations run a query immediately. Operators that return a single value, such as Count, Sum, First, and Any, must read the elements when you call them so they can produce that value.

List<string> workItems = ["design", "docs"];

IEnumerable<string> query = workItems.Where(item => item.StartsWith('d'));

// Count() reads the matching elements right when this line runs.
int count = query.Count();
Console.WriteLine($"Count before add: {count}");

workItems.Add("deploy");

Console.WriteLine($"Stored count: {count}");
Console.WriteLine($"Current count: {query.Count()}");
// This example produces the following output:
//
//    Count before add: 2
//    Stored count: 2
//    Current count: 3
//

There's no single trigger that runs a query. Eager evaluation, also called immediate evaluation, runs the query right away and stores or returns the result. Scalar and aggregate operators such as Count, Sum, First, and Any use eager evaluation. So do ToList and ToArray when you need a snapshot of the current results. For more information about deferred execution, see Introduction to LINQ Queries in the LINQ documentation.

Compose queries

Real queries often grow from smaller decisions: start with a shared filter, add a sort for one screen, or apply an optional condition from user input. By composing queries, you can keep each step readable and reuse common parts instead of repeating one large query.

Imagine an app that shows open work items in several places. One view needs all open items, and another view needs only the highest-priority open items. To compose a query, start with a base query stored in a variable, then build a more specific query from it. Because execution is deferred, each step describes more of the result. No work happens until you enumerate the final query.

The following example stores the open work items in one query, then reuses that query to find the highest-priority open items:

(string Title, int Priority, bool IsOpen)[] items =
[
    ("docs", 2, true),
    ("tests", 1, true),
    ("deploy", 3, false),
    ("api", 1, true)
];

IEnumerable<(string Title, int Priority, bool IsOpen)> openItems =
    from item in items
    where item.IsOpen
    select item;

IEnumerable<string> topOpenItems =
    from item in openItems
    where item.Priority == 1
    orderby item.Title
    select item.Title;

foreach (string title in topOpenItems)
{
    Console.WriteLine(title);
}
// This example produces the following output:
//
//    api
//    tests
//

You can also compose queries with method syntax. The next example starts with all open items, then conditionally adds another filter before it selects the titles:

(string Title, string Area, bool IsOpen)[] items =
[
    ("write docs", "docs", true),
    ("fix tests", "tests", true),
    ("deploy site", "deploy", false)
];

bool onlyDocs = true;

IEnumerable<(string Title, string Area, bool IsOpen)> query =
    items.Where(item => item.IsOpen);

if (onlyDocs)
{
    query = query.Where(item => item.Area == "docs");
}

foreach (string title in query.Select(item => item.Title))
{
    Console.WriteLine(title); // => write docs
}

Composition can use either or both eager evaluation and deferred execution. Materialize a shared intermediate result with ToList or ToArray when you want that part to run once and stay fixed. Then build another deferred query from the cached results for the final output:

List<(string Title, int Priority, bool IsOpen)> items =
[
    ("docs", 2, true),
    ("tests", 1, true),
    ("deploy", 3, false)
];

List<(string Title, int Priority, bool IsOpen)> openItems = items
    .Where(item => item.IsOpen)
    .ToList();

items.Add(("api", 1, true));

IEnumerable<string> cachedTopOpenItems = openItems
    .Where(item => item.Priority == 1)
    .OrderBy(item => item.Title)
    .Select(item => item.Title);

foreach (string title in cachedTopOpenItems)
{
    Console.WriteLine(title); // => tests
}

Explore LINQ in depth

This article uses in-memory collections to teach LINQ syntax and core operators. For more information about LINQ operators and advanced scenarios, see Language Integrated Query (LINQ). That article covers joins, XML, database providers, dynamic queries, and custom operators.

See also