What are LINQ queries, and how do they work in .NET?
Understanding LINQ
Language Integrated Query (LINQ) is a set of features in .NET that provides a consistent, expressive, and type-safe way to query and manipulate data from different sources. LINQ bridges the gap between the world of objects and the world of data.
// Basic LINQ query example
var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// Query syntax
var evenNumbersQuery = from n in numbers
                       where n % 2 == 0
                       select n;
// Method syntax
var evenNumbersMethod = numbers.Where(n => n % 2 == 0);
foreach (var number in evenNumbersQuery)
{
    Console.WriteLine(number); // Outputs: 2, 4, 6, 8, 10
}LINQ Providers
LINQ can work with different data sources through various providers:
- LINQ to Objects: Query in-memory collections that implement 
IEnumerable<T> - LINQ to Entities: Query databases through Entity Framework
 - LINQ to SQL: Query SQL Server databases (older technology)
 - LINQ to XML: Query and manipulate XML documents
 - LINQ to DataSet: Query ADO.NET DataSets
 - Custom LINQ Providers: Query custom data sources
 
// LINQ to Objects
var numbers = new List<int> { 1, 2, 3, 4, 5 };
var evenNumbers = numbers.Where(n => n % 2 == 0);
// LINQ to Entities (Entity Framework)
var youngCustomers = dbContext.Customers
    .Where(c => c.Age < 30)
    .OrderBy(c => c.LastName)
    .ToList();
// LINQ to XML
XDocument doc = XDocument.Load("data.xml");
var elements = from e in doc.Descendants("Customer")
               where (int)e.Attribute("Age") < 30
               select e;
// LINQ to DataSet
var youngCustomers = dataSet.Tables["Customers"].AsEnumerable()
    .Where(row => row.Field<int>("Age") < 30)
    .CopyToDataTable();Query Syntax vs. Method Syntax
LINQ provides two syntax options for writing queries:
Query Syntax
// Query syntax (SQL-like)
var result = from c in customers
             where c.City == "London"
             orderby c.Name
             select new { c.Name, c.Phone };Method Syntax
// Method syntax (using extension methods)
var result = customers
    .Where(c => c.City == "London")
    .OrderBy(c => c.Name)
    .Select(c => new { c.Name, c.Phone });Both syntaxes are functionally equivalent, but:
- Query syntax is often more readable for complex queries
 - Method syntax provides more functionality and flexibility
 
Key LINQ Operations
Filtering
// Where - filter elements based on a predicate
var adults = people.Where(p => p.Age >= 18);
// OfType - filter elements based on type
var strings = mixedList.OfType<string>();Sorting
// OrderBy - sort in ascending order
var orderedByName = people.OrderBy(p => p.LastName);
// OrderByDescending - sort in descending order
var orderedByAgeDesc = people.OrderByDescending(p => p.Age);
// ThenBy - secondary sort
var orderedPeople = people
    .OrderBy(p => p.LastName)
    .ThenBy(p => p.FirstName);Projection
// Select - transform elements
var names = people.Select(p => p.Name);
// SelectMany - flatten nested collections
var allPhoneNumbers = customers.SelectMany(c => c.PhoneNumbers);
// Anonymous types
var customerInfo = customers.Select(c => new { c.Name, c.City, IsAdult = c.Age >= 18 });Joining
// Join - inner join
var query = from c in customers
            join o in orders on c.CustomerId equals o.CustomerId
            select new { c.Name, o.OrderDate, o.Amount };
// Method syntax join
var result = customers.Join(
    orders,
    customer => customer.CustomerId,
    order => order.CustomerId,
    (customer, order) => new { customer.Name, order.OrderDate, order.Amount }
);
// GroupJoin - left outer join with grouping
var customerOrders = from c in customers
                     join o in orders on c.CustomerId equals o.CustomerId into customerOrders
                     select new { Customer = c, Orders = customerOrders };Grouping
// GroupBy - group elements by a key
var customersByCity = customers.GroupBy(c => c.City);
// Using grouping
foreach (var cityGroup in customersByCity)
{
    Console.WriteLine($"City: {cityGroup.Key}");
    foreach (var customer in cityGroup)
    {
        Console.WriteLine($"  {customer.Name}");
    }
}
// Query syntax with grouping
var ordersByCustomer = from o in orders
                       group o by o.CustomerId into customerOrders
                       select new
                       {
                           CustomerId = customerOrders.Key,
                           OrderCount = customerOrders.Count(),
                           TotalAmount = customerOrders.Sum(o => o.Amount)
                       };Aggregation
// Count - count elements
int count = numbers.Count();
int evenCount = numbers.Count(n => n % 2 == 0);
// Sum - sum values
decimal totalAmount = orders.Sum(o => o.Amount);
// Min/Max - find minimum/maximum values
int minAge = people.Min(p => p.Age);
int maxAge = people.Max(p => p.Age);
// Average - calculate average
double averageAge = people.Average(p => p.Age);
// Aggregate - custom aggregation
int product = numbers.Aggregate(1, (acc, n) => acc * n);Element Operations
// First/FirstOrDefault - get first element
var firstCustomer = customers.First();
var firstLondonCustomer = customers.First(c => c.City == "London");
var firstOrDefault = customers.FirstOrDefault(c => c.City == "Paris"); // null if none found
// Last/LastOrDefault - get last element
var lastCustomer = customers.Last();
var lastOrDefault = customers.LastOrDefault(c => c.City == "Paris");
// Single/SingleOrDefault - get single element (throws if multiple)
var uniqueCustomer = customers.Single(c => c.CustomerId == 12345);
var uniqueOrDefault = customers.SingleOrDefault(c => c.CustomerId == 12345);
// ElementAt/ElementAtOrDefault - get element at index
var thirdCustomer = customers.ElementAt(2);
var tenthOrDefault = customers.ElementAtOrDefault(9); // default if out of rangeSet Operations
// Distinct - remove duplicates
var uniqueNames = names.Distinct();
// Union - combine sets, remove duplicates
var allCustomers = ukCustomers.Union(usCustomers);
// Intersect - common elements
var customersInBothLists = listA.Intersect(listB);
// Except - elements in first set but not in second
var customersOnlyInA = listA.Except(listB);Quantifiers
// Any - check if any element satisfies condition
bool hasAdults = people.Any(p => p.Age >= 18);
// All - check if all elements satisfy condition
bool allAdults = people.All(p => p.Age >= 18);
// Contains - check if collection contains element
bool containsJohn = names.Contains("John");Partitioning
// Take - get first n elements
var topFive = customers.OrderByDescending(c => c.Purchases).Take(5);
// Skip - skip first n elements
var afterTopFive = customers.OrderByDescending(c => c.Purchases).Skip(5);
// TakeLast - get last n elements (EF Core 2.1+)
var bottomFive = customers.OrderByDescending(c => c.Purchases).TakeLast(5);
// SkipLast - skip last n elements (EF Core 2.1+)
var notBottomFive = customers.OrderByDescending(c => c.Purchases).SkipLast(5);
// TakeWhile - take elements while condition is true
var takeWhileUnder40 = ages.TakeWhile(age => age < 40);
// SkipWhile - skip elements while condition is true
var skipWhileUnder40 = ages.SkipWhile(age => age < 40);Deferred Execution
LINQ queries use deferred execution, meaning they’re not executed until the results are actually needed.
// Query definition (not executed yet)
var query = numbers.Where(n => n > 5);
// Add more numbers to the source collection
numbers.Add(10);
numbers.Add(20);
// Query execution (includes the newly added numbers)
foreach (var n in query)
{
    Console.WriteLine(n);
}Forcing Immediate Execution
// ToList - execute query and return results as List<T>
var results = query.ToList();
// ToArray - execute query and return results as array
var resultsArray = query.ToArray();
// ToDictionary - execute query and return results as Dictionary<TKey, TValue>
var customerDict = customers.ToDictionary(c => c.CustomerId);
// ToLookup - execute query and return results as ILookup<TKey, TElement>
var customersByCity = customers.ToLookup(c => c.City);
// Count, Sum, Average, etc. - execute query and return a scalar result
int count = query.Count();LINQ and IQueryable
When working with LINQ to Entities (Entity Framework), queries are translated to SQL:
// LINQ to Entities query
var londonCustomers = dbContext.Customers
    .Where(c => c.City == "London")
    .OrderBy(c => c.Name)
    .Select(c => new { c.Name, c.Email });
    
// The query above is translated to SQL like:
// SELECT [c].[Name], [c].[Email]
// FROM [Customers] AS [c]
// WHERE [c].[City] = N'London'
// ORDER BY [c].[Name]IQueryable vs. IEnumerable
IQueryable<T>: Represents a query that will be executed at the data sourceIEnumerable<T>: Represents a query that will be executed in memory
// IQueryable - filtering happens at the database
IQueryable<Customer> queryable = dbContext.Customers.Where(c => c.City == "London");
// IEnumerable - all data is loaded, then filtered in memory
IEnumerable<Customer> enumerable = dbContext.Customers.AsEnumerable().Where(c => c.City == "London");LINQ Query Execution Pipeline
- Data source: The collection or data source to query
 - Query creation: Define the query using LINQ operators
 - Query execution: Iterate over the results or force execution
 - Result processing: Process the query results
 
// 1. Data source
List<Customer> customers = GetCustomers();
// 2. Query creation
var query = from c in customers
            where c.Orders.Count > 10
            orderby c.Name
            select new { c.Name, OrderCount = c.Orders.Count };
// 3. Query execution
// 4. Result processing
foreach (var item in query) // Execution happens here
{
    Console.WriteLine($"{item.Name}: {item.OrderCount} orders");
}Advanced LINQ Techniques
Parallel LINQ (PLINQ)
// Parallel query execution
var parallelQuery = numbers.AsParallel()
    .Where(n => IsPrime(n))
    .OrderBy(n => n);
    
// Force parallel execution with specific options
var customParallelQuery = numbers.AsParallel()
    .WithDegreeOfParallelism(4)
    .WithExecutionMode(ParallelExecutionMode.ForceParallelism)
    .Where(n => IsPrime(n));Custom LINQ Operators
// Extension method to create custom LINQ operator
public static class LinqExtensions
{
    public static IEnumerable<T> WhereNot<T>(this IEnumerable<T> source, Func<T, bool> predicate)
    {
        return source.Where(item => !predicate(item));
    }
}
// Usage
var nonAdults = people.WhereNot(p => p.Age >= 18);Expression Trees
// Creating an expression tree manually
Expression<Func<Customer, bool>> expr = c => c.City == "London";
// Examining the expression tree
var parameter = expr.Parameters[0]; // c
var body = expr.Body; // c.City == "London"
// Building expression trees dynamically
ParameterExpression param = Expression.Parameter(typeof(Customer), "c");
MemberExpression property = Expression.Property(param, "City");
ConstantExpression constant = Expression.Constant("London");
BinaryExpression equality = Expression.Equal(property, constant);
var lambdaExpr = Expression.Lambda<Func<Customer, bool>>(equality, param);
var compiledFunc = lambdaExpr.Compile();
// Using the compiled expression
var result = customers.Where(compiledFunc);Interview Tips
Define LINQ clearly: LINQ is a set of features in .NET that provides a consistent way to query and manipulate data from different sources.
Syntax options: Explain the difference between query syntax and method syntax, and when each might be preferred.
Deferred execution: Emphasize that LINQ queries use deferred execution, meaning they’re not executed until the results are needed.
Common operations: Be familiar with filtering, sorting, projection, joining, and grouping operations.
LINQ providers: Understand the different LINQ providers (LINQ to Objects, LINQ to Entities, LINQ to XML) and how they translate queries.
Performance considerations: Discuss how to optimize LINQ queries, especially when working with databases.
IQueryable vs. IEnumerable: Explain the difference and when to use each interface.
Test Your Knowledge
Take a quick quiz to test your understanding of this topic.