LINQ: Complete Developer Learning Guide
Learn Language Integrated Query from fundamentals to advanced data querying with C# 14, .NET 10, EF Core 10, practical examples, and performance practices.
Introduction
Applications constantly work with collections of data. A developer may need to find a particular record, filter a list, sort results, transform objects, group related items, or combine information from multiple sources. These operations appear in web applications, desktop software, APIs, background services, and data-processing systems.
LINQ (Language Integrated Query) is a set of .NET language features and APIs that lets developers express these data operations in a consistent, readable way. With C#, LINQ can query in-memory collections, database-backed query providers, XML, and other supported data sources.
For .NET developers, LINQ is especially important because it is used with arrays, lists, dictionaries, Entity Framework Core, and many other .NET libraries. The same foundational ideas are also useful to developers working with other languages and query tools.
This guide begins with simple collection queries and gradually moves toward query composition, execution behavior, provider differences, EF Core, performance, and practical application patterns.
What Is LINQ?
LINQ stands for Language Integrated Query. It brings query capabilities directly into .NET languages such as C#. Instead of writing a separate loop for every filtering, sorting, or grouping task, you can describe the operation using standard query operators.
LINQ is not a database, an ORM, or a standalone programming language. It is a query capability made available through language syntax, extension methods, delegates, expression trees, and query providers.
A simple example
int[] numbers = { 5, 12, 3, 20, 8, 15 };
var results = numbers
.Where(number => number > 10)
.OrderBy(number => number);
foreach (int number in results)
{
Console.WriteLine(number);
}
The query filters the collection to values greater than 10 and then sorts those values in ascending order. The result is 12, 15, and 20.
Why Developers Use LINQ
| Benefit | What it means |
|---|---|
| Readable queries | Filtering, sorting, grouping, and projection can be expressed clearly. |
| Consistent approach | Many data sources expose familiar query operators. |
| Strong typing | The compiler can validate many query operations and result types. |
| Less repetitive code | Common data transformations need fewer manual loops. |
| Composable operations | Small operations can be chained into a larger query. |
| IDE support | IntelliSense, refactoring, and compile-time feedback improve development. |
LINQ does not automatically make every query faster or simpler. A clear loop can be preferable for complex stateful logic, and a poorly designed LINQ query can still perform badly. Choose it because it expresses the operation well, not simply because it is available.
Where LINQ Can Be Used
LINQ operators work with different data sources through different implementations. The source and provider matter because they determine which operations are supported and where the work takes place.
| Data source | Typical interface/provider | Example use |
|---|---|---|
| Arrays and Lists | IEnumerable<T> / LINQ to Objects | Filter products in memory. |
| Entity Framework Core | IQueryable<T> provider | Query rows from a relational database. |
| XML | LINQ to XML | Read and transform XML documents. |
| Other providers | Provider-specific query implementation | Query supported remote or specialized data sources. |
LINQ to Objects
LINQ to Objects queries in-memory collections such as arrays, lists, and other enumerable sequences. The operators run in your application process.
LINQ with EF Core
Entity Framework Core accepts LINQ expressions and translates supported parts of a query into the database provider's query language, commonly SQL for relational databases. The database executes the translated query and returns data to the application.
List<T> and a LINQ query over an EF Core DbSet<T> may look similar, but they do not necessarily execute in the same place or support exactly the same operations.How LINQ Works
A LINQ query typically starts with a data source, applies one or more query operations, and produces a result. With LINQ to Objects, operators commonly work through delegates. With IQueryable, a provider can inspect an expression tree and translate supported operations.
A conceptual view of a LINQ query lifecycle.
Core building blocks
- Source: The collection or queryable data source.
- Operator: An operation such as filtering, projection, ordering, or grouping.
- Lambda expression: A compact function or expression supplied to an operator.
- Execution: The point at which the query is evaluated.
- Result: A sequence, a single value, or a grouped structure.
Query Syntax vs Method Syntax
C# supports two common ways to write LINQ queries. Both express query operations, and the compiler translates query expressions into method calls.
1. Query syntax
int[] scores = { 45, 80, 67, 92, 55 };
var passedScores =
from score in scores
where score >= 60
orderby score descending
select score;
2. Method syntax
var passedScores = scores
.Where(score => score >= 60)
.OrderByDescending(score => score);
| Query syntax | Method syntax |
|---|---|
Uses keywords such as from, where, orderby, and select. | Uses extension methods such as Where, OrderBy, and Select. |
| Can read naturally for query-shaped operations. | Works naturally with method chaining and lambdas. |
| Some operators are not directly represented by query keywords. | Exposes the full standard operator set. |
Developer recommendation: Learn both, but become comfortable with method syntax because it is common in everyday C# and EF Core code. Use whichever form makes the intent easiest to understand.
Modern C# 14 note: extension members
LINQ operators such as Where, Select, and OrderBy are commonly exposed as extension methods. C# 14 adds extension blocks, which let you define extension methods and extension properties using a newer syntax. This does not replace LINQ or change how its built-in operators work; it gives library authors another way to design APIs around existing types.
public static class SequenceExtensions
{
extension<T>(IEnumerable<T> source)
{
public bool IsEmpty => !source.Any();
}
}
// Usage
bool hasNoEmployees = employees.IsEmpty;
this-parameter extension methods remain supported. Use C# 14 extension blocks when their property or grouped-member syntax makes a custom API clearer; do not create custom wrappers for standard LINQ operators without a real need.Your First LINQ Query
Before using LINQ, create a collection. The following example uses a list of integers.
List numbers = new() { 10, 25, 5, 40, 15 };
var selectedNumbers = numbers.Where(n => n > 10);
foreach (var number in selectedNumbers)
{
Console.WriteLine(number);
}
Where receives a predicate: a function that returns true for values that should remain in the result. Here, only numbers greater than 10 are selected.
Understanding the lambda expression
n => n > 10
nrepresents the current item being examined.=>separates the parameter from the expression.n > 10is the condition being evaluated.
A lambda is not exclusive to LINQ. It is a C# language feature used to represent anonymous functions and, in expression-tree contexts, expressions that can be inspected by a provider.
Filtering and Projection
Where – filter data
Where returns the elements that satisfy a condition. It does not change the shape of each selected element.
var activeEmployees = employees
.Where(employee => employee.IsActive);
Select – project data
Select transforms each source element into a new value. This is called projection.
var employeeNames = employees
.Select(employee => employee.Name);
Select into an anonymous object
var employeeSummaries = employees.Select(employee => new
{
employee.Id,
employee.Name,
employee.Department,
AnnualSalary = employee.MonthlySalary * 12
});
| Operator | Purpose | Typical result |
|---|---|---|
Where | Keep matching elements. | Same element type, fewer items. |
Select | Transform each element. | Projected values or objects. |
SelectMany | Flatten nested sequences into one sequence. | One combined sequence. |
SelectMany – flatten nested data
var allSkills = employees
.SelectMany(employee => employee.Skills);
If each employee has a collection of skills, SelectMany produces one sequence containing the skills from all employees.
Where decides which items stay. Select decides what each item becomes. SelectMany expands and flattens nested collections.Sorting and Reversing
Ordering operators arrange elements according to one or more keys.
var ascending = employees
.OrderBy(employee => employee.Name);
var descending = employees
.OrderByDescending(employee => employee.MonthlySalary);
var orderedByDepartmentThenName = employees
.OrderBy(employee => employee.Department)
.ThenBy(employee => employee.Name);
var reverseOrder = numbers.Reverse();
| Operator | Purpose |
|---|---|
OrderBy | Sort ascending by a key. |
OrderByDescending | Sort descending by a key. |
ThenBy | Add another ascending sort key. |
ThenByDescending | Add another descending sort key. |
Reverse | Reverse the current sequence order; it is not a replacement for sorting. |
OrderBy again starts a new primary ordering. Use ThenBy when you want a secondary sort key.Grouping and Lookup
GroupBy
GroupBy creates groups that share the same key. Each group exposes a key and the elements belonging to it.
var employeesByDepartment = employees
.GroupBy(employee => employee.Department);
foreach (var group in employeesByDepartment)
{
Console.WriteLine(group.Key);
foreach (var employee in group)
{
Console.WriteLine(employee.Name);
}
}
Grouping with a summary
var departmentSummary = employees
.GroupBy(employee => employee.Department)
.Select(group => new
{
Department = group.Key,
EmployeeCount = group.Count(),
AverageSalary = group.Average(x => x.MonthlySalary)
});
ToLookup
ToLookup creates a lookup structure that supports key-based access to one or more matching elements. Unlike GroupBy, it is evaluated immediately and can be accessed repeatedly by key.
var employeesLookup = employees
.ToLookup(employee => employee.Department);
var salesEmployees = employeesLookup["Sales"];
| GroupBy | ToLookup |
|---|---|
| Returns a grouped sequence. | Returns an ILookup<TKey,TElement>. |
| Typically deferred until enumerated. | Builds the lookup immediately. |
| Useful for grouping and further query composition. | Useful for repeated key-based access to grouped items. |
Joining Data
Join operators combine related data from two sequences. In business applications, this commonly means connecting employees to departments, orders to customers, or products to categories.
Join – matching records
var employeeDepartments =
employees.Join(
departments,
employee => employee.DepartmentId,
department => department.Id,
(employee, department) => new
{
EmployeeName = employee.Name,
DepartmentName = department.Name
});
GroupJoin – one parent with matching children
var departmentsWithEmployees =
departments.GroupJoin(
employees,
department => department.Id,
employee => employee.DepartmentId,
(department, departmentEmployees) => new
{
Department = department.Name,
Employees = departmentEmployees
});
LeftJoin and RightJoin – direct outer joins in .NET 10
.NET 10 adds first-class LeftJoin and RightJoin operators to LINQ. A left join keeps every item from the first sequence and includes matching items from the second sequence when available. A right join keeps every item from the second sequence.
// .NET 10: keep every employee, even without a matching department
var employeesWithDepartments = employees.LeftJoin(
departments,
employee => employee.DepartmentId,
department => department.Id,
(employee, department) => new
{
Employee = employee.Name,
Department = department == null ? "Unassigned" : department.Name
});
// .NET 10: keep every department, even when it has no employees
var departmentsWithEmployees = employees.RightJoin(
departments,
employee => employee.DepartmentId,
department => department.Id,
(employee, department) => new
{
Department = department.Name,
Employee = employee == null ? "No employee" : employee.Name
});
GroupJoin plus DefaultIfEmpty pattern shown below.Traditional left outer join pattern (before .NET 10)
In earlier .NET versions, a common left-join pattern uses join, into, and DefaultIfEmpty. This keeps the left-side item even when no matching right-side item exists.
var result =
from department in departments
join employee in employees
on department.Id equals employee.DepartmentId
into employeeGroup
from employee in employeeGroup.DefaultIfEmpty()
select new
{
Department = department.Name,
Employee = employee == null ? "No employee" : employee.Name
};
Relational EF Core providers may translate recognized join patterns into SQL joins. Translation depends on the query shape and provider.
Aggregation and Quantifier Operators
Aggregation operators
| Operator | Purpose | Example |
|---|---|---|
Count | Count elements. | employees.Count() |
LongCount | Count using a 64-bit result. | employees.LongCount() |
Sum | Calculate a total. | employees.Sum(x => x.MonthlySalary) |
Min | Find the minimum value. | numbers.Min() |
Max | Find the maximum value. | numbers.Max() |
Average | Calculate the average. | numbers.Average() |
Aggregate | Apply a custom accumulation operation. | numbers.Aggregate(...) |
decimal totalSalary = employees.Sum(x => x.MonthlySalary);
decimal averageSalary = employees.Average(x => x.MonthlySalary);
int employeeCount = employees.Count();
Quantifier operators
- Any: Checks whether at least one element exists or matches a condition.
- All: Checks whether every element satisfies a condition. An empty sequence returns true.
- Contains: Checks whether a sequence contains a specified value, using the applicable equality comparison.
bool hasManagers = employees.Any(x => x.Role == "Manager");
bool allActive = employees.All(x => x.IsActive);
bool hasNumber = numbers.Contains(20);
Any rather than retrieving all matching records and then counting them.Element and Set Operators
Element operators
| Operator | Behavior |
|---|---|
First | Returns the first element; throws if the sequence is empty. |
FirstOrDefault | Returns the first element or the default value if none exists. |
Single | Requires exactly one matching element; throws for zero or multiple matches. |
SingleOrDefault | Allows zero or one match; throws if multiple matches exist. |
Last | Returns the last element; throws if empty. |
LastOrDefault | Returns the last element or default when empty. |
ElementAt | Returns the element at a zero-based position; throws if out of range. |
ElementAtOrDefault | Returns the element at a position or default if unavailable. |
Choose the operator based on the rule your application expects. For example, use Single when more than one match indicates a data-integrity problem. Use FirstOrDefault when no match is an expected outcome.
Set operators
var distinctDepartments = employees
.Select(x => x.Department)
.Distinct();
var combined = firstList.Union(secondList);
var common = firstList.Intersect(secondList);
var onlyInFirst = firstList.Except(secondList);
Distinct, Union, Intersect, and Except use equality semantics. For custom objects, understand whether the type's equality implementation or a supplied comparer matches the business rule.
Partitioning and Chunking
Partitioning operators select a portion of a sequence or skip a portion before returning the remainder.
var firstFive = employees.Take(5);
var remaining = employees.Skip(5);
var pageTwo = employees
.OrderBy(x => x.Id)
.Skip(10)
.Take(10);
var batches = employees.Chunk(100);
| Operator | Purpose |
|---|---|
Take | Returns up to the requested number of initial elements. |
Skip | Skips the requested number of initial elements. |
TakeWhile | Returns elements while a condition remains true, stopping at the first failure. |
SkipWhile | Skips elements while a condition remains true, then returns the rest. |
Chunk | Splits a sequence into arrays of a maximum specified size. |
Skip and Take. Without ordering, page boundaries may be inconsistent. For large or frequently changing datasets, consider keyset pagination where appropriate.Deferred and Immediate Execution
One of the most important LINQ concepts is knowing when a query actually runs. Many sequence-producing operators build a query that is evaluated only when the result is enumerated. Other operators execute immediately and return a scalar or materialized collection.
Deferred execution
var query = numbers.Where(n => n > 10);
// The query is evaluated when enumerated.
foreach (var number in query)
{
Console.WriteLine(number);
}
Immediate execution
var resultList = numbers
.Where(n => n > 10)
.ToList();
int count = numbers.Count();
Typical behavior for LINQ sequence operators; exact behavior depends on the operator and provider.
Common deferred operators
Where, Select, OrderBy, and GroupBy generally return a sequence that is evaluated when enumerated in LINQ to Objects.
Common immediate operators
ToList, ToArray, Count, Any, First, Sum, and Average evaluate the operation to produce a result.
ToList or ToArray.IEnumerable vs IQueryable
These interfaces are central to understanding LINQ in .NET applications, especially when working with EF Core.
IEnumerable<T>
IEnumerable<T> represents a sequence that can be enumerated. LINQ to Objects operators work with it and execute .NET code against the elements as they are enumerated.
IQueryable<T>
IQueryable<T> extends the query model with an expression tree and a query provider. A provider can inspect the expression and decide how to execute or translate it. EF Core uses this model to translate supported LINQ queries into database queries.
| Aspect | IEnumerable<T> | IQueryable<T> |
|---|---|---|
| Represents | An enumerable sequence. | A provider-backed query. |
| Query representation | Delegates are commonly used by LINQ to Objects. | Expression trees represent query operations. |
| Execution | Usually in the application process. | Determined by the provider; may execute remotely. |
| Typical use | In-memory collections. | EF Core database queries and other providers. |
| Translation | No database SQL translation by LINQ to Objects. | Provider may translate supported expressions. |
The interface affects how query operations are represented and executed.
A common EF Core example
IQueryable query = dbContext.Employees
.Where(x => x.IsActive);
var employees = await query
.OrderBy(x => x.Name)
.ToListAsync();
The filter and ordering are composed into the query before ToListAsync executes it. By contrast, calling AsEnumerable changes subsequent query composition to LINQ-to-Objects operations when enumerated.
ToList too early on a large EF Core query unless you intentionally want to load those rows into memory. Filtering after materialization may process far more data than necessary.Expression Trees and Query Providers
An expression tree represents code as data: a structured model of an expression that another component can inspect. This is different from a compiled delegate, which is executable code.
For example, a predicate supplied to LINQ to Objects is commonly compiled and executed as .NET code. When an expression is supplied to an IQueryable provider, the provider can inspect the expression tree and translate supported parts.
Expression> condition =
employee => employee.IsActive;
Why providers matter
- A provider determines which operations it can translate.
- Different providers may support different functions or translation patterns.
- A query that works against a list may not translate when used with EF Core.
- Translation failures should be understood and addressed rather than hidden by loading an unnecessarily large dataset.
LINQ with Entity Framework Core
This section uses EF Core 10, the .NET 10-compatible LTS release. EF Core uses LINQ as its primary query interface. You write a query against a DbSet<T>, EF Core builds a query representation, translates supported operations for the configured database provider, and materializes the returned records into .NET objects.
Typical query flow
Example: filtering and projection in the database
var employeeDtos = await dbContext.Employees
.Where(x => x.IsActive)
.OrderBy(x => x.Name)
.Select(x => new EmployeeDto
{
Id = x.Id,
Name = x.Name,
DepartmentId = x.DepartmentId
})
.ToListAsync();
This query asks the provider to filter and order the records and return only the projected columns required by the DTO, if the provider can translate the expression as expected.
EF Core 10: LINQ query enhancements
EF Core 10 recognizes the .NET 10 LeftJoin and RightJoin operators in supported relational queries, making outer-join queries more direct than the older GroupJoin and DefaultIfEmpty pattern. EF Core 10 also includes provider-specific query capabilities and improvements; always check the provider documentation before relying on a particular translation.
var studentsWithDepartments = await dbContext.Students
.LeftJoin(
dbContext.Departments,
student => student.DepartmentId,
department => department.Id,
(student, department) => new
{
student.FirstName,
DepartmentName = department == null
? "Unassigned"
: department.Name
})
.ToListAsync();
Tracking vs no-tracking queries
By default, EF Core tracks entity instances returned from queries so that changes can later be detected and saved. For read-only scenarios, AsNoTracking can avoid change-tracking overhead.
var employees = await dbContext.Employees
.AsNoTracking()
.Where(x => x.IsActive)
.ToListAsync();
Use tracking when you intend to update entities through the same context. Use no-tracking queries when the results are read-only, after considering identity resolution and the shape of the query.
Async Database Queries
When using EF Core with a relational database, asynchronous terminal operators allow the application thread to be released while database I/O is in progress. This is especially useful in web applications handling concurrent requests.
var employees = await dbContext.Employees
.Where(x => x.IsActive)
.ToListAsync();
var employee = await dbContext.Employees
.FirstOrDefaultAsync(x => x.Id == employeeId);
bool exists = await dbContext.Employees
.AnyAsync(x => x.Email == email);
Common EF Core async terminal methods
| Async method | Purpose |
|---|---|
ToListAsync | Materialize query results into a list. |
ToArrayAsync | Materialize query results into an array. |
FirstOrDefaultAsync | Return the first match or default. |
SingleOrDefaultAsync | Return zero or one match; fail if multiple matches exist. |
AnyAsync | Check whether a match exists. |
CountAsync | Count matching records. |
SaveChangesAsync | Asynchronously persist tracked changes; it is not a LINQ query operator. |
Where and Select usually compose the query. EF Core async terminal methods such as ToListAsync execute it. Do not run multiple concurrent operations on the same DbContext instance.Performance and Best Practices
- Filter early: Apply selective filters before materializing data when using a remote provider.
- Project only required fields: Use
Selectto avoid loading unnecessary columns or entity graphs. - Avoid premature materialization: Do not call
ToListbefore all database-side filters and projections have been applied. - Use Any for existence checks: Prefer
Anyover loading records just to check whether one exists. - Use AsNoTracking for read-only EF Core queries: Choose tracking behavior deliberately.
- Inspect generated SQL: Use EF Core logging or query inspection tools to understand what reaches the database.
- Watch for N+1 queries: Avoid repeatedly loading related data inside loops. Consider projection, explicit loading, or suitable eager loading.
- Use stable ordering for pagination: Add a unique tie-breaker when necessary.
- Measure before optimizing: Compare query plans, database indexes, network transfer, and application processing.
- Keep queries readable: Extract complex expressions into well-named methods or query-building components when that improves clarity and testability.
Example: avoid loading every row first
// Less efficient when Employees is a database-backed DbSet:
var allEmployees = await dbContext.Employees.ToListAsync();
var activeEmployees = allEmployees
.Where(x => x.IsActive)
.ToList();
// Prefer composing the filter before execution:
var activeEmployees = await dbContext.Employees
.Where(x => x.IsActive)
.ToListAsync();
The first approach transfers all employee rows before filtering in application memory. The second allows EF Core to translate the filter so the database can return only matching rows, assuming normal relational translation.
Common Mistakes
| Mistake | Why it is a problem | Better approach |
|---|---|---|
Calling ToList too early | May move unnecessary data into memory. | Compose database filters and projections first. |
Using Single when duplicates are valid | Throws when more than one result exists. | Choose First, FirstOrDefault, or a uniqueness rule intentionally. |
Using OrderBy instead of ThenBy | Replaces the primary ordering. | Use ThenBy for additional sort keys. |
| Assuming all C# methods translate to SQL | Providers support only specific translations. | Check provider support and generated SQL. |
| Enumerating an expensive query repeatedly | May repeat work or database calls. | Materialize once when a stable snapshot is needed. |
| Ignoring null and empty results | Can cause exceptions or incorrect assumptions. | Choose the appropriate terminal operator and validate data. |
| Using client-side filtering on huge datasets | Can consume memory and transfer excessive data. | Keep translatable filtering on the server. |
Practical Example: Employee Queries
Assume an application has an employee collection with properties such as Id, Name, Department, MonthlySalary, and IsActive.
Find active employees
var activeEmployees = employees
.Where(x => x.IsActive)
.ToList();
Find employees earning above a threshold
var highEarners = employees
.Where(x => x.MonthlySalary >= 50000)
.OrderByDescending(x => x.MonthlySalary)
.ToList();
Return a compact employee view
var employeeList = employees.Select(x => new
{
x.Id,
x.Name,
x.Department
}).ToList();
Count employees by department
var departmentCounts = employees
.GroupBy(x => x.Department)
.Select(group => new
{
Department = group.Key,
TotalEmployees = group.Count()
})
.ToList();
Check whether an email is already used
bool emailExists = employees
.Any(x => x.Email == enteredEmail);
Build a paged result
int pageNumber = 2;
int pageSize = 10;
var page = employees
.OrderBy(x => x.Id)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToList();
For an EF Core query, keep the source as IQueryable until the final asynchronous materialization step, and apply database-supported filters, ordering, and projection before execution.
LINQ and Other Data Access Technologies
LINQ is often discussed alongside EF Core, Dapper, and ADO.NET, but they solve different parts of data access.
| Technology | Primary responsibility | How it relates to LINQ |
|---|---|---|
| LINQ | Express queries and transformations in .NET. | The query capability used directly in C#. |
| Entity Framework Core | ORM for mapping .NET entities to database data. | Uses LINQ queries and translates supported expressions. |
| Dapper | Lightweight object mapping over database commands. | Typically uses SQL directly; it is not an EF-style LINQ query provider by default. |
| ADO.NET | Low-level database connectivity and commands. | LINQ can be used on in-memory results after data is loaded, but ADO.NET itself is not a LINQ provider. |
Knowing these boundaries helps you select the right tool. LINQ is a way to express queries; EF Core, Dapper, and ADO.NET are data-access technologies with different abstractions and trade-offs.
Important LINQ Interview Questions
1. What is LINQ?
LINQ stands for Language Integrated Query. It provides a consistent way to query and transform data in .NET languages.
2. What are the two common LINQ syntaxes?
Query syntax and method syntax. Query syntax uses query keywords; method syntax uses extension methods and lambdas.
3. What is the difference between Where and Select?
Where filters elements. Select projects each element into a new value or shape.
4. What is deferred execution?
It means a query is evaluated when its results are enumerated rather than necessarily when the query is declared.
5. What is the difference between IEnumerable and IQueryable?
IEnumerable represents an enumerable sequence. IQueryable also carries an expression tree and provider, allowing provider-specific translation and execution.
6. What is the difference between First and Single?
First returns the first matching element. Single requires exactly one matching element and throws if there are zero or multiple matches.
7. What is SelectMany used for?
It projects each element to a sequence and flattens the resulting sequences into one sequence.
8. What is the difference between GroupBy and ToLookup?
GroupBy returns a grouped sequence and is generally deferred. ToLookup builds an immediately available key-based lookup.
9. How does EF Core use LINQ?
EF Core uses expression trees and a query provider to translate supported LINQ operations into database queries.
10. Why should ToList be used carefully with EF Core?
It executes and materializes the query. Calling it too early can load unnecessary rows into application memory and prevent later operations from being translated to the database.
11. What is the purpose of AsNoTracking?
It tells EF Core not to track returned entities for change detection, which can reduce overhead for suitable read-only queries.
12. Does every LINQ query execute in SQL?
No. LINQ to Objects executes in memory. An EF Core provider translates supported query expressions to the database; unsupported or client-side operations need careful handling.
Using C# 14 and .NET 10 in this guide
For a new project, target .NET 10 to use the C# 14 compiler by default. You normally do not need to set LangVersion manually when the project targets net10.0 with the .NET 10 SDK.
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
LINQ itself is primarily a .NET API and query model, while C# supplies the language syntax used to compose queries. C# 14 features such as extension blocks can help you create your own query-related APIs, but not every C# 14 feature is specifically a LINQ feature. This guide focuses on language and library changes that are relevant to everyday querying rather than adding unrelated language features.
net10.0 gives you the .NET 10 API surface and the C# 14 language version by default. EF Core 10 requires the .NET 10 SDK and runtime; it does not run on earlier .NET versions or .NET Framework.Official Learning Resources
Use official documentation to verify operator behavior, API availability, provider translation, and current EF Core guidance.
- Microsoft Learn: Language Integrated Query (LINQ)
- Standard Query Operators
- Introduction to LINQ Queries
- Deferred Execution and Lazy Evaluation
- IQueryable Interface
- EF Core: Querying Data
- EF Core: Efficient Querying
- What's New in C# 14
- What's New in .NET 10
- What's New in EF Core 10
- LINQ Join Operations, including .NET 10 LeftJoin and RightJoin
Final Thoughts
LINQ gives .NET developers a consistent and expressive way to query, filter, transform, group, and combine data. Its real value is not simply knowing the operators, but understanding how a query behaves, when it executes, and where the work is performed.
As you apply LINQ in modern applications, pay particular attention to the difference between in-memory queries and provider-based queries such as EF Core. Keep database operations efficient, avoid materializing data too early, and choose operators that clearly communicate your intent.
With the examples and concepts in this guide, you have a foundation for using LINQ confidently with C# 14 and .NET 10. Continue practising with realistic collections and database queries, and use the official documentation to check provider-specific behavior and API details.