Section 9 of 10

LINQ: Language Integrated Query

🎯 What You'll Learn

  • What LINQ is and why it's a game-changer for C# developers
  • Query syntax vs method syntax
  • Filtering with Where
  • Transforming with Select
  • Sorting with OrderBy and OrderByDescending
  • Aggregations: Count, Sum, Average, Min, Max
  • Grouping data with GroupBy
  • Joining collections with Join
  • Deferred execution and materialization
  • Building powerful InvenTrack reports and analytics

What is LINQ?

LINQ (Language Integrated Query) is a set of features that brings powerful query capabilities directly into C#. It lets you query collections, databases, XML, and more using a consistent, readable syntax that feels like SQL but works with any data source.

💡 Key Concept

LINQ transforms how you work with data. Instead of writing loops and conditionals to filter, sort, and transform collections, you write declarative queries that express what you want, not how to get it. The result is code that's shorter, clearer, and less error-prone.

Think of LINQ like asking a librarian for books instead of searching yourself. You say "Give me all science fiction books published after 2020, sorted by title" and the librarian handles the details. With loops, you'd walk every aisle, check every book, and sort them yourself. LINQ is the librarian.

The Problem: Traditional Loops

WithoutLINQ.cs C#
// Find all products under $100, sorted by price
List<Product> products = GetProducts();
List<Product> affordable = new();

// Filter
foreach (Product p in products)
{
    if (p.Price < 100m)
    {
        affordable.Add(p);
    }
}

// Sort
affordable.Sort((a, b) => a.Price.CompareTo(b.Price));

// Tedious, error-prone, hard to read

The Solution: LINQ

WithLINQ.cs C#
using System.Linq;

// Same result, one elegant line
var affordable = products
    .Where(p => p.Price < 100m)
    .OrderBy(p => p.Price);

// Clear, concise, expressive!
💡 Method Syntax vs Query Syntax

LINQ has two syntaxes: method syntax (using extension methods like .Where()) and query syntax (SQL-like keywords). Method syntax is more common and powerful. We'll focus on it, but show query syntax too.

Filtering with Where

WhereExamples.cs C#
List<Product> products = GetProducts();

// Products under $100
var cheap = products.Where(p => p.Price < 100m);

// Low stock products
var lowStock = products.Where(p => p.IsLowStock);

// Multiple conditions
var expensiveAndLowStock = products
    .Where(p => p.Price > 500m && p.IsLowStock);

// Name contains "Dell"
var dellProducts = products
    .Where(p => p.Name.Contains("Dell"));

Transforming with Select

SelectExamples.cs C#
// Get just the names
IEnumerable<string> names = products.Select(p => p.Name);

// Get just the prices
IEnumerable<decimal> prices = products.Select(p => p.Price);

// Transform to anonymous type
var summary = products.Select(p => new
{
    p.Name,
    p.Price,
    Value = p.GetInventoryValue()
});

// Chain Where and Select
var expensiveNames = products
    .Where(p => p.Price > 500m)
    .Select(p => p.Name);

Sorting

OrderingExamples.cs C#
// Sort by price (ascending)
var byPrice = products.OrderBy(p => p.Price);

// Sort by price (descending)
var byPriceDesc = products.OrderByDescending(p => p.Price);

// Sort by name
var byName = products.OrderBy(p => p.Name);

// Multiple sort keys (ThenBy)
var sorted = products
    .OrderBy(p => p.Name)
    .ThenByDescending(p => p.Price);

Aggregations

AggregationExamples.cs C#
// Count
int totalProducts = products.Count();
int expensiveCount = products.Count(p => p.Price > 500m);

// Sum
decimal totalValue = products.Sum(p => p.GetInventoryValue());

// Average
decimal avgPrice = products.Average(p => p.Price);

// Min and Max
decimal cheapest = products.Min(p => p.Price);
decimal mostExpensive = products.Max(p => p.Price);

// Any and All
bool hasLowStock = products.Any(p => p.IsLowStock);
bool allInStock = products.All(p => p.QuantityInStock > 0);

Grouping

GroupByExamples.cs C#
// Group products by price range
var byPriceRange = products.GroupBy(p =>
{
    if (p.Price < 100m) return "Budget";
    if (p.Price < 500m) return "Mid-Range";
    return "Premium";
});

foreach (var group in byPriceRange)
{
    Console.WriteLine($"\n{group.Key} Products:");
    foreach (var product in group)
    {
        Console.WriteLine($"  {product.Name}: {product.Price:C}");
    }
}

Taking and Skipping

TakeSkipExamples.cs C#
// Top 5 most expensive
var top5 = products
    .OrderByDescending(p => p.Price)
    .Take(5);

// Skip first 10, take next 5 (pagination)
var page2 = products
    .OrderBy(p => p.Name)
    .Skip(10)
    .Take(5);

// First and FirstOrDefault
Product first = products.First();
Product firstDell = products.FirstOrDefault(p => p.Name.Contains("Dell"));

Deferred Execution

⚠️ Important: Deferred Execution

LINQ queries don't execute immediately! They execute when you iterate (foreach) or materialize (ToList(), ToArray(), Count()). This is called deferred execution.

DeferredExecution.cs C#
List<int> numbers = new() { 1, 2, 3 };

// Query defined, but NOT executed yet
var query = numbers.Where(n => n > 1);

// Modify the source
numbers.Add(4);

// NOW it executes - includes 4!
foreach (int n in query)  // 2, 3, 4
{
    Console.WriteLine(n);
}

// Materialize immediately with ToList()
var snapshot = numbers.Where(n => n > 1).ToList();
numbers.Add(5);  // Won't affect snapshot

InvenTrack: Powerful Reports with LINQ

InvenTrackReports.cs C#
class InventoryReports
{
    private List<Product> products;
    
    public InventoryReports(List<Product> products)
    {
        this.products = products;
    }
    
    public void LowStockReport()
    {
        var lowStock = products
            .Where(p => p.IsLowStock)
            .OrderBy(p => p.QuantityInStock)
            .Select(p => new 
            { 
                p.Sku, 
                p.Name, 
                p.QuantityInStock, 
                p.ReorderLevel 
            });
        
        Console.WriteLine("=== LOW STOCK ALERT ===");
        foreach (var item in lowStock)
        {
            Console.WriteLine($"{item.Sku}: {item.Name} - {item.QuantityInStock} (reorder at {item.ReorderLevel})");
        }
    }
    
    public void ValueReport()
    {
        var topValue = products
            .OrderByDescending(p => p.GetInventoryValue())
            .Take(10)
            .Select(p => new
            {
                p.Name,
                Value = p.GetInventoryValue()
            });
        
        Console.WriteLine("\n=== TOP 10 BY VALUE ===");
        foreach (var item in topValue)
        {
            Console.WriteLine($"{item.Name}: {item.Value:C}");
        }
    }
    
    public void SummaryStatistics()
    {
        var stats = new
        {
            TotalProducts = products.Count(),
            TotalValue = products.Sum(p => p.GetInventoryValue()),
            AveragePrice = products.Average(p => p.Price),
            CheapestPrice = products.Min(p => p.Price),
            MostExpensivePrice = products.Max(p => p.Price),
            LowStockCount = products.Count(p => p.IsLowStock)
        };
        
        Console.WriteLine("\n=== INVENTORY STATISTICS ===");
        Console.WriteLine($"Total Products: {stats.TotalProducts}");
        Console.WriteLine($"Total Value: {stats.TotalValue:C}");
        Console.WriteLine($"Average Price: {stats.AveragePrice:C}");
        Console.WriteLine($"Price Range: {stats.CheapestPrice:C} - {stats.MostExpensivePrice:C}");
        Console.WriteLine($"Low Stock Items: {stats.LowStockCount}");
    }
}
🚀 LINQ in Action

These reports would require dozens of lines with traditional loops. With LINQ, each report is just a few expressive lines. This is the power of declarative programming!

Common LINQ Methods Reference

Method Purpose Returns
Where Filter items by condition IEnumerable<T>
Select Transform each item IEnumerable<TResult>
OrderBy Sort ascending IOrderedEnumerable<T>
OrderByDescending Sort descending IOrderedEnumerable<T>
GroupBy Group by key IEnumerable<IGrouping>
Count Count items int
Sum Sum values numeric type
Average Average value numeric type
Min/Max Minimum/Maximum T
First/FirstOrDefault First item (or default) T
Any Check if any match bool
All Check if all match bool
Take Take first N items IEnumerable<T>
Skip Skip first N items IEnumerable<T>
ToList Materialize to List List<T>
ToArray Materialize to Array T[]

Key Takeaways

  • LINQ brings SQL-like querying to C# collections
  • Method syntax uses extension methods like .Where(), .Select()
  • Lambda expressions (=>) define query conditions
  • Deferred execution: queries don't run until you iterate or materialize
  • Chain operations to build complex queries from simple parts
  • Filtering: Where, First, Any, All
  • Transformation: Select projects to new shapes
  • Ordering: OrderBy, ThenBy
  • Aggregation: Count, Sum, Average, Min, Max
  • LINQ makes code more readable, maintainable, and expressive
🎯 Congratulations!

You've completed the core C# fundamentals! You now understand variables, control flow, methods, OOP, collections, and LINQ. In the final section of Part I, we'll explore Asynchronous Programming with async/await—essential for building responsive, scalable applications that don't freeze while waiting for I/O operations.