LINQ Introduction

Advanced 30 min read Lesson 12 of 13

What is LINQ?

LINQ (Language Integrated Query) is a powerful feature in C# that lets you query collections (like lists, arrays, and databases) using C# syntax. Think of it like SQL for C# - but much more powerful!

Query
Ask questions about your data

Filter
Find exactly what you need

Transform
Change data into new shapes

Think of it like this: Imagine you have a big box of LEGO pieces. LINQ is like a magic tool that can find all red pieces, sort them by size, and group them by shape - all in one go!

Why is LINQ Important?

Benefits
  • Readable: Code looks like English sentences
  • Powerful: Complex queries in few lines
  • Consistent: Same syntax for arrays, lists, databases
  • Type-safe: Compiler catches errors early
  • IntelliSense: Visual Studio helps you write queries
  • Productive: Write less code, do more
Real-World Examples
  • 📊 Find all customers who spent more than $100
  • 📅 Get all orders from last month
  • 🏷️ Group products by category
  • 📈 Sort employees by salary
  • 🔍 Search for products by name
  • 📋 Get top 10 best-selling items

When Was LINQ Introduced?

LINQ was introduced in .NET 3.5 (2007) as a game-changer for C# developers. Before LINQ, querying data required writing loops and complex code.

Before LINQ
// Find adults - old way
List<Person> adults = new List<Person>();
foreach (var person in people)
{
    if (person.Age >= 18)
    {
        adults.Add(person);
    }
}
With LINQ
// Find adults - LINQ way
var adults = people
    .Where(p => p.Age >= 18)
    .ToList();
Today, LINQ is an essential skill for every C# developer!

Sample Data

We'll use this sample data throughout the lesson:

// ===== PERSON CLASS =====
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string City { get; set; }
    public decimal Salary { get; set; }
}

// ===== SAMPLE DATA =====
List<Person> people = new List<Person>
{
    new Person { Name = "Alice", Age = 25, City = "New York", Salary = 75000 },
    new Person { Name = "Bob", Age = 30, City = "London", Salary = 65000 },
    new Person { Name = "Charlie", Age = 22, City = "New York", Salary = 55000 },
    new Person { Name = "Diana", Age = 28, City = "Paris", Salary = 82000 },
    new Person { Name = "Eve", Age = 35, City = "London", Salary = 90000 }
};

Where - Filtering Data

Where() is the most common LINQ method. It filters a collection based on a condition.

// ===== BASIC WHERE =====
// Find all adults (age >= 18)
var adults = people.Where(p => p.Age >= 18);

// ===== MULTIPLE CONDITIONS =====
// Find people from New York who are over 20
var newYorkersOver20 = people.Where(p => p.City == "New York" && p.Age > 20);

// ===== REAL-WORLD: Find high earners =====
// Find people earning more than 70000
var highEarners = people.Where(p => p.Salary > 70000);

// ===== CHAINING WHERE =====
var result = people
    .Where(p => p.Age > 25)
    .Where(p => p.City == "London");
Remember: Where() returns a new collection containing only the items that match your condition.

Select - Projection (Transforming Data)

Select() transforms each item in a collection. It's like taking a shape and turning it into a new shape.

// ===== SELECT SPECIFIC PROPERTIES =====
// Get only names
var names = people.Select(p => p.Name);
// Result: ["Alice", "Bob", "Charlie", "Diana", "Eve"]

// ===== CREATE NEW OBJECTS =====
// Create anonymous objects with name and age
var nameAge = people.Select(p => new { p.Name, p.Age });

// ===== TRANSFORM DATA =====
// Create greeting strings
var greetings = people.Select(p => $"Hello, {p.Name} from {p.City}!");

// ===== CALCULATE WITH SELECT =====
// Get annual salary (monthly * 12)
var annualSalaries = people.Select(p => new 
{ 
    p.Name, 
    AnnualSalary = p.Salary * 12 
});
Pro Tip: Select() is powerful for shaping data - you can extract, transform, or create new objects from your data.

OrderBy - Sorting Data

OrderBy() sorts your data in ascending order. Use OrderByDescending() for descending order.

// ===== BASIC SORTING =====
// Sort by name (A to Z)
var sortedByName = people.OrderBy(p => p.Name);

// Sort by age (oldest first)
var sortedByAgeDesc = people.OrderByDescending(p => p.Age);

// ===== MULTIPLE SORT CRITERIA =====
// Sort by city, then by name
var sortedMulti = people
    .OrderBy(p => p.City)
    .ThenBy(p => p.Name);

// Sort by salary descending, then by age ascending
var sortedSalary = people
    .OrderByDescending(p => p.Salary)
    .ThenBy(p => p.Age);
Remember: OrderBy() doesn't change the original collection - it returns a new sorted collection.

GroupBy - Grouping Data

GroupBy() groups items that share a common property. It's like sorting LEGO pieces by color!

// ===== BASIC GROUPING =====
// Group people by city
var groupedByCity = people.GroupBy(p => p.City);

// Loop through groups
foreach (var group in groupedByCity)
{
    Console.WriteLine($"City: {group.Key}");
    foreach (var person in group)
    {
        Console.WriteLine($"  {person.Name}");
    }
}

// ===== GROUP WITH COUNT =====
// Count people in each city
var cityCounts = people
    .GroupBy(p => p.City)
    .Select(g => new { City = g.Key, Count = g.Count() });

// ===== GROUP WITH AVERAGE =====
// Average age by city
var avgAgeByCity = people
    .GroupBy(p => p.City)
    .Select(g => new 
    { 
        City = g.Key, 
        AverageAge = g.Average(p => p.Age) 
    });
Pro Tip: GroupBy is perfect for summarizing data - counts, averages, sums, and more!

Aggregate Functions

Aggregate functions calculate values from a collection.

// ===== SUM =====
int totalAge = people.Sum(p => p.Age);        // Total of all ages
decimal totalSalary = people.Sum(p => p.Salary); // Total of all salaries

// ===== AVERAGE =====
double avgAge = people.Average(p => p.Age);        // Average age
decimal avgSalary = people.Average(p => p.Salary); // Average salary

// ===== MAX AND MIN =====
int maxAge = people.Max(p => p.Age);          // Oldest person
int minAge = people.Min(p => p.Age);          // Youngest person
decimal highestSalary = people.Max(p => p.Salary); // Highest salary

// ===== COUNT =====
int totalCount = people.Count();                     // Total people
int countOver25 = people.Count(p => p.Age > 25); // People over 25

// ===== REAL-WORLD: Salary Statistics =====
var stats = new
{
    TotalEmployees = people.Count(),
    AverageSalary = people.Average(p => p.Salary),
    HighestSalary = people.Max(p => p.Salary),
    LowestSalary = people.Min(p => p.Salary),
    TotalPayroll = people.Sum(p => p.Salary)
};

First and Single

These methods get specific items from a collection.

// ===== FIRST / FIRSTORDEFAULT =====
// Get the first person
var first = people.First();                    // Throws if empty
var firstSafe = people.FirstOrDefault();        // Returns null if empty

// First from New York
var firstNY = people.FirstOrDefault(p => p.City == "New York");

// ===== SINGLE / SINGLEORDEFAULT =====
// Get exactly one person (throws if not exactly one)
var single = people.Single(p => p.Name == "Alice");

// ===== ANY - Check if any exist =====
bool hasBob = people.Any(p => p.Name == "Bob");      // true
bool hasAlice = people.Any(p => p.Name == "Alice");  // true
bool hasFrank = people.Any(p => p.Name == "Frank"); // false

// ===== ALL - Check if ALL match =====
bool allAdults = people.All(p => p.Age >= 18);    // true
bool allFromNY = people.All(p => p.City == "New York"); // false
Important: First() and Single() throw exceptions if no items match. Use FirstOrDefault() and SingleOrDefault() for safety.

Query Syntax vs Method Syntax

LINQ has two ways to write queries. Both do the same thing - it's just different styles.

Query Syntax

Looks like SQL, more readable for complex queries

var adults = from p in people
             where p.Age >= 18
             orderby p.Name
             select p;
Method Syntax

Uses methods like Where(), OrderBy(), Select()

var adults = people
    .Where(p => p.Age >= 18)
    .OrderBy(p => p.Name)
    .ToList();
Pro Tip: Most developers use Method Syntax because it's more consistent and works with all LINQ features. Choose whichever you prefer!

Real-World Examples

📊 Employee Reports
// Get top 3 highest paid
var topPaid = employees
    .OrderByDescending(e => e.Salary)
    .Take(3)
    .ToList();

// Get employees by department
var byDept = employees
    .GroupBy(e => e.Department)
    .Select(g => new {
        Dept = g.Key,
        Count = g.Count(),
        AvgSalary = g.Average(e => e.Salary)
    });
🛒 Product Analysis
// Find expensive products
var expensive = products
    .Where(p => p.Price > 100)
    .OrderByDescending(p => p.Price)
    .ToList();

// Products by category
var byCategory = products
    .GroupBy(p => p.Category)
    .Select(g => new {
        Category = g.Key,
        Count = g.Count(),
        TotalValue = g.Sum(p => p.Price * p.Stock)
    });
📅 Order Analytics
// Orders from last 30 days
var recent = orders
    .Where(o => o.Date >= DateTime.Now.AddDays(-30))
    .OrderByDescending(o => o.Date)
    .ToList();

// Top customers
var topCustomers = orders
    .GroupBy(o => o.CustomerId)
    .Select(g => new {
        CustomerId = g.Key,
        TotalSpent = g.Sum(o => o.Total)
    })
    .OrderByDescending(c => c.TotalSpent)
    .Take(10);
🔍 Search Functionality
// Search products
var searchResults = products
    .Where(p => p.Name.Contains(searchTerm) ||
                p.Description.Contains(searchTerm))
    .OrderBy(p => p.Name)
    .ToList();

// Filter with multiple criteria
var filtered = products
    .Where(p => p.Price >= minPrice &&
                p.Price <= maxPrice &&
                p.Category == category)
    .ToList();

Quick Reference

Method Purpose Example
Where() Filter items .Where(p => p.Age > 18)
Select() Transform items .Select(p => p.Name)
OrderBy() Sort ascending .OrderBy(p => p.Name)
OrderByDescending() Sort descending .OrderByDescending(p => p.Age)
GroupBy() Group items .GroupBy(p => p.City)
Sum() Total sum .Sum(p => p.Salary)
Average() Average value .Average(p => p.Age)
Count() Number of items .Count(p => p.Age > 25)
First() First item .First(p => p.City == "NY")
Any() Check if any match .Any(p => p.Name == "Bob")

Exercise: Employee Analytics

Task: Use LINQ to analyze employee data.

Instructions:
  1. Create a list of employees with: Name, Department, Salary, YearsOfExperience
  2. Write LINQ queries to:
    • Get all employees making more than $60,000
    • Get the average salary by department
    • Get the top 3 highest paid employees
    • Get employees sorted by years of experience (descending)
    • Get the total salary for each department
    • Check if any employee has more than 20 years experience
    • Get the employee with the highest salary in each department
  3. Use at least 5 different LINQ methods
  4. Use both query and method syntax (at least one of each)
Hints:
  • Use Where() for filtering
  • Use GroupBy() for department grouping
  • Use OrderByDescending() for sorting
  • Use Take() for top 3
  • Use Sum() and Average() for calculations
Key Takeaway

LINQ makes working with data simple and powerful:

✅ Use Where() to filter data
✅ Use Select() to transform data
✅ Use OrderBy() to sort data
✅ Use GroupBy() to group data
✅ Use Sum(), Average(), Count() for calculations
✅ Use Any(), All() to check conditions
✅ LINQ works with any collection - lists, arrays, databases
✅ LINQ makes your code more readable and less error-prone

Test Your Knowledge - Take Quiz