LEARN BUILD GROW TOGETHER
DEVELOPER LEARNING HUB
Learn. Build. Grow Together.

Practical tutorials, real-world examples and interview preparation.

.NETC#SQL SERVERANGULAR
Home /.NET /LINQ – Complete Developer Learning Guide

LINQ – Complete Developer Learning Guide

LINQ in C# 14 & .NET 10 – Complete Developer Learning Guide | Skills Tutorials

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.

Version baseline: Examples target .NET 10, C# 14, and EF Core 10. When an API is specific to .NET 10, it is identified clearly. Older alternatives are included where they help you maintain applications on earlier .NET versions.
Learning approach: We will use a small employee and department dataset in examples. The focus is on understanding the query, the result it produces, and why a particular LINQ operator is appropriate. Examples progress from in-memory collections to database-backed queries so you can see what changes between LINQ to Objects and EF Core.

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.

Remember: LINQ describes what data you want and how it should be shaped. The LINQ provider or source determines how that query is executed.

Why Developers Use LINQ

BenefitWhat it means
Readable queriesFiltering, sorting, grouping, and projection can be expressed clearly.
Consistent approachMany data sources expose familiar query operators.
Strong typingThe compiler can validate many query operations and result types.
Less repetitive codeCommon data transformations need fewer manual loops.
Composable operationsSmall operations can be chained into a larger query.
IDE supportIntelliSense, 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 sourceTypical interface/providerExample use
Arrays and ListsIEnumerable<T> / LINQ to ObjectsFilter products in memory.
Entity Framework CoreIQueryable<T> providerQuery rows from a relational database.
XMLLINQ to XMLRead and transform XML documents.
Other providersProvider-specific query implementationQuery 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.

Important distinction: A LINQ query over a 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.

Data SourceList / DbSet / XML LINQ OperationsWhere / Select / OrderBy ExecutionEnumerate / Materialize ResultSequence / Value The exact execution path depends on the source and its LINQ provider.

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 syntaxMethod 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;
Remember: Traditional 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
  • n represents the current item being examined.
  • => separates the parameter from the expression.
  • n > 10 is 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
});
OperatorPurposeTypical result
WhereKeep matching elements.Same element type, fewer items.
SelectTransform each element.Projected values or objects.
SelectManyFlatten 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.

Easy way to remember: 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();
OperatorPurpose
OrderBySort ascending by a key.
OrderByDescendingSort descending by a key.
ThenByAdd another ascending sort key.
ThenByDescendingAdd another descending sort key.
ReverseReverse the current sequence order; it is not a replacement for sorting.
Important: Calling 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"];
GroupByToLookup
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
    });
Version note: These operators are available in .NET 10. For EF Core queries, use EF Core 10 with a compatible provider and verify provider translation for your query. Earlier .NET versions use the 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

OperatorPurposeExample
CountCount elements.employees.Count()
LongCountCount using a 64-bit result.employees.LongCount()
SumCalculate a total.employees.Sum(x => x.MonthlySalary)
MinFind the minimum value.numbers.Min()
MaxFind the maximum value.numbers.Max()
AverageCalculate the average.numbers.Average()
AggregateApply 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);
Practical tip: If you only need to know whether a matching record exists, use Any rather than retrieving all matching records and then counting them.

Element and Set Operators

Element operators

OperatorBehavior
FirstReturns the first element; throws if the sequence is empty.
FirstOrDefaultReturns the first element or the default value if none exists.
SingleRequires exactly one matching element; throws for zero or multiple matches.
SingleOrDefaultAllows zero or one match; throws if multiple matches exist.
LastReturns the last element; throws if empty.
LastOrDefaultReturns the last element or default when empty.
ElementAtReturns the element at a zero-based position; throws if out of range.
ElementAtOrDefaultReturns 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);
OperatorPurpose
TakeReturns up to the requested number of initial elements.
SkipSkips the requested number of initial elements.
TakeWhileReturns elements while a condition remains true, stopping at the first failure.
SkipWhileSkips elements while a condition remains true, then returns the rest.
ChunkSplits a sequence into arrays of a maximum specified size.
Pagination warning: Apply a stable, deterministic ordering before 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();
Deferred execution Build query No execution yet Enumerate query Execution begins when consumed. Immediate execution Build query Materialize / scalar ToList · ToArray · Count · Sum The operation runs at the call.

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.

Why this matters: Enumerating a deferred query multiple times can repeat its work. If you need a stable in-memory snapshot or repeated access, materialize deliberately with 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.

AspectIEnumerable<T>IQueryable<T>
RepresentsAn enumerable sequence.A provider-backed query.
Query representationDelegates are commonly used by LINQ to Objects.Expression trees represent query operations.
ExecutionUsually in the application process.Determined by the provider; may execute remotely.
Typical useIn-memory collections.EF Core database queries and other providers.
TranslationNo database SQL translation by LINQ to Objects.Provider may translate supported expressions.
IEnumerable<T> In-memory sequence .NET evaluates elements IQueryable<T> Expression tree Provider translates / executes

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.

Important: Do not call 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.
Developer perspective: Understanding expression trees explains why EF Core can convert certain C# query expressions into SQL, while LINQ to Objects simply evaluates operations against in-memory values.

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

C# LINQWhere / Select EF CoreQuery translation SQL QueryProvider-specific DatabaseExecutes SQL ObjectsResults Simplified relational-provider flow; actual internals vary by provider and query.

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 methodPurpose
ToListAsyncMaterialize query results into a list.
ToArrayAsyncMaterialize query results into an array.
FirstOrDefaultAsyncReturn the first match or default.
SingleOrDefaultAsyncReturn zero or one match; fail if multiple matches exist.
AnyAsyncCheck whether a match exists.
CountAsyncCount matching records.
SaveChangesAsyncAsynchronously persist tracked changes; it is not a LINQ query operator.
Do not confuse: LINQ operators such as 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

  1. Filter early: Apply selective filters before materializing data when using a remote provider.
  2. Project only required fields: Use Select to avoid loading unnecessary columns or entity graphs.
  3. Avoid premature materialization: Do not call ToList before all database-side filters and projections have been applied.
  4. Use Any for existence checks: Prefer Any over loading records just to check whether one exists.
  5. Use AsNoTracking for read-only EF Core queries: Choose tracking behavior deliberately.
  6. Inspect generated SQL: Use EF Core logging or query inspection tools to understand what reaches the database.
  7. Watch for N+1 queries: Avoid repeatedly loading related data inside loops. Consider projection, explicit loading, or suitable eager loading.
  8. Use stable ordering for pagination: Add a unique tie-breaker when necessary.
  9. Measure before optimizing: Compare query plans, database indexes, network transfer, and application processing.
  10. 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

MistakeWhy it is a problemBetter approach
Calling ToList too earlyMay move unnecessary data into memory.Compose database filters and projections first.
Using Single when duplicates are validThrows when more than one result exists.Choose First, FirstOrDefault, or a uniqueness rule intentionally.
Using OrderBy instead of ThenByReplaces the primary ordering.Use ThenBy for additional sort keys.
Assuming all C# methods translate to SQLProviders support only specific translations.Check provider support and generated SQL.
Enumerating an expensive query repeatedlyMay repeat work or database calls.Materialize once when a stable snapshot is needed.
Ignoring null and empty resultsCan cause exceptions or incorrect assumptions.Choose the appropriate terminal operator and validate data.
Using client-side filtering on huge datasetsCan 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.

TechnologyPrimary responsibilityHow it relates to LINQ
LINQExpress queries and transformations in .NET.The query capability used directly in C#.
Entity Framework CoreORM for mapping .NET entities to database data.Uses LINQ queries and translates supported expressions.
DapperLightweight object mapping over database commands.Typically uses SQL directly; it is not an EF-style LINQ query provider by default.
ADO.NETLow-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.

Remember: Targeting 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.

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.

Skills Tutorials

Learn • Build • Grow Together