Skip to content

C# LINQ (Language Integrated Query)

Overview

LINQ (Language Integrated Query) provides a unified query syntax for working with data from different sources. It allows you to write queries directly in C# code.

Basic LINQ Concepts

LINQ Query Syntax

csharp
using System.Linq;

// Data source
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// Query syntax
var evenNumbers = from num in numbers
                 where num % 2 == 0
                 select num;

// Method syntax (equivalent)
var evenNumbersMethod = numbers.Where(num => num % 2 == 0);

// Execute query
foreach (var num in evenNumbers)
{
    Console.WriteLine(num); // 2, 4, 6, 8, 10
}

LINQ Method Syntax

csharp
List<string> words = new List<string> 
{ 
    "apple", "banana", "cherry", "date", "elderberry" 
};

// Filtering
var longWords = words.Where(w => w.Length > 5);

// Projection
var wordLengths = words.Select(w => new { Word = w, Length = w.Length });

// Ordering
var sortedWords = words.OrderBy(w => w);

// Grouping
var wordsByLength = words.GroupBy(w => w.Length);

LINQ Operators

Filtering Operators

csharp
public class LinqFiltering
{
    public static void DemonstrateFiltering()
    {
        var products = new[]
        {
            new { Id = 1, Name = "Laptop", Price = 999.99m, Category = "Electronics" },
            new { Id = 2, Name = "Book", Price = 19.99m, Category = "Books" },
            new { Id = 3, Name = "Phone", Price = 699.99m, Category = "Electronics" },
            new { Id = 4, Name = "Pen", Price = 2.99m, Category = "Office" }
        };
        
        // Where clause
        var electronics = products.Where(p => p.Category == "Electronics");
        var expensive = products.Where(p => p.Price > 500);
        var cheapBooks = products.Where(p => p.Category == "Books" && p.Price < 20);
        
        Console.WriteLine("Electronics:");
        foreach (var item in electronics)
        {
            Console.WriteLine($"{item.Name}: ${item.Price}");
        }
    }
}

Projection Operators

csharp
public class LinqProjection
{
    public static void DemonstrateProjection()
    {
        var students = new[]
        {
            new { FirstName = "John", LastName = "Doe", Age = 20, Grade = 85 },
            new { FirstName = "Jane", LastName = "Smith", Age = 22, Grade = 92 },
            new { FirstName = "Bob", LastName = "Johnson", Age = 21, Grade = 78 }
        };
        
        // Select - transform to new type
        var studentNames = students.Select(s => $"{s.FirstName} {s.LastName}");
        var studentInfo = students.Select(s => new 
        { 
            FullName = $"{s.FirstName} {s.LastName}", 
            IsAdult = s.Age >= 18 
        });
        
        // SelectMany - flatten collections
        var classes = new[]
        {
            new { Name = "Math", Students = new[] { "Alice", "Bob" } },
            new { Name = "Science", Students = new[] { "Charlie", "David", "Eve" } }
        };
        
        var allStudents = classes.SelectMany(c => c.Students);
        
        Console.WriteLine("All students:");
        foreach (var student in allStudents)
        {
            Console.WriteLine(student);
        }
    }
}

Ordering Operators

csharp
public class LinqOrdering
{
    public static void DemonstrateOrdering()
    {
        var people = new[]
        {
            new { Name = "Alice", Age = 30, Salary = 50000 },
            new { Name = "Bob", Age = 25, Salary = 45000 },
            new { Name = "Charlie", Age = 35, Salary = 60000 },
            new { Name = "David", Age = 28, Salary = 52000 }
        };
        
        // OrderBy
        var byAge = people.OrderBy(p => p.Age);
        var byName = people.OrderBy(p => p.Name);
        
        // OrderByDescending
        var bySalaryDesc = people.OrderByDescending(p => p.Salary);
        
        // ThenBy for secondary sorting
        var byAgeThenName = people
            .OrderBy(p => p.Age)
            .ThenBy(p => p.Name);
        
        Console.WriteLine("People ordered by age:");
        foreach (var person in byAge)
        {
            Console.WriteLine($"{person.Name}, Age: {person.Age}");
        }
    }
}

Grouping Operators

csharp
public class LinqGrouping
{
    public static void DemonstrateGrouping()
    {
        var products = new[]
        {
            new { Name = "Laptop", Category = "Electronics", Price = 999.99m },
            new { Name = "Mouse", Category = "Electronics", Price = 29.99m },
            new { Name = "Book", Category = "Books", Price = 19.99m },
            new { Name = "Pen", Category = "Office", Price = 2.99m },
            new { Name = "Phone", Category = "Electronics", Price = 699.99m }
        };
        
        // GroupBy
        var groupedByCategory = products.GroupBy(p => p.Category);
        
        foreach (var group in groupedByCategory)
        {
            Console.WriteLine($"Category: {group.Key}");
            foreach (var product in group)
            {
                Console.WriteLine($"  {product.Name}: ${product.Price}");
            }
        }
        
        // GroupBy with projection
        var categoryStats = products
            .GroupBy(p => p.Category)
            .Select(g => new 
            {
                Category = g.Key,
                Count = g.Count(),
                TotalPrice = g.Sum(p => p.Price),
                AveragePrice = g.Average(p => p.Price)
            });
        
        Console.WriteLine("\nCategory statistics:");
        foreach (var stat in categoryStats)
        {
            Console.WriteLine($"{stat.Category}: {stat.Count} items, " +
                          $"Avg: ${stat.AveragePrice:F2}");
        }
    }
}

Set Operations

csharp
public class LinqSetOperations
{
    public static void DemonstrateSetOperations()
    {
        var set1 = new[] { 1, 2, 3, 4, 5 };
        var set2 = new[] { 4, 5, 6, 7, 8 };
        
        // Union
        var union = set1.Union(set2);
        Console.WriteLine($"Union: {string.Join(", ", union)}");
        
        // Intersection
        var intersection = set1.Intersect(set2);
        Console.WriteLine($"Intersection: {string.Join(", ", intersection)}");
        
        // Except
        var except = set1.Except(set2);
        Console.WriteLine($"Except: {string.Join(", ", except)}");
        
        // Distinct
        var numbers = new[] { 1, 2, 2, 3, 3, 3, 4 };
        var distinct = numbers.Distinct();
        Console.WriteLine($"Distinct: {string.Join(", ", distinct)}");
    }
}

LINQ with Collections

LINQ with Lists

csharp
public class LinqWithLists
{
    public static void DemonstrateListOperations()
    {
        var employees = new List<Employee>
        {
            new Employee { Id = 1, Name = "Alice", Department = "IT", Salary = 60000 },
            new Employee { Id = 2, Name = "Bob", Department = "HR", Salary = 50000 },
            new Employee { Id = 3, Name = "Charlie", Department = "IT", Salary = 65000 },
            new Employee { Id = 4, Name = "David", Department = "Finance", Salary = 55000 }
        };
        
        // Find specific employee
        var employee = employees.FirstOrDefault(e => e.Id == 2);
        
        // Filter by department
        var itEmployees = employees.Where(e => e.Department == "IT").ToList();
        
        // Calculate statistics
        var totalSalary = employees.Sum(e => e.Salary);
        var averageSalary = employees.Average(e => e.Salary);
        var maxSalary = employees.Max(e => e.Salary);
        
        // Check conditions
        var hasHighEarner = employees.Any(e => e.Salary > 70000);
        var allHaveSalary = employees.All(e => e.Salary > 0);
        
        Console.WriteLine($"Total salary: ${totalSalary:N2}");
        Console.WriteLine($"Average salary: ${averageSalary:N2}");
        Console.WriteLine($"Max salary: ${maxSalary:N2}");
    }
}

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Department { get; set; }
    public decimal Salary { get; set; }
}

LINQ with Dictionaries

csharp
public class LinqWithDictionaries
{
    public static void DemonstrateDictionaryOperations()
    {
        var productPrices = new Dictionary<string, decimal>
        {
            ["Laptop"] = 999.99m,
            ["Mouse"] = 29.99m,
            ["Keyboard"] = 79.99m,
            ["Monitor"] = 299.99m
        };
        
        // Filter dictionary
        var expensive = productPrices.Where(kvp => kvp.Value > 100);
        
        // Transform to different type
        var productList = productPrices
            .Select(kvp => new { Name = kvp.Key, Price = kvp.Value })
            .OrderBy(p => p.Price);
        
        // Group by price range
        var priceGroups = productPrices
            .GroupBy(kvp => kvp.Value < 50 ? "Cheap" : 
                           kvp.Value < 200 ? "Medium" : "Expensive");
        
        Console.WriteLine("Products by price range:");
        foreach (var group in priceGroups)
        {
            Console.WriteLine($"{group.Key}:");
            foreach (var item in group)
            {
                Console.WriteLine($"  {item.Key}: ${item.Value}");
            }
        }
    }
}

Advanced LINQ

LINQ with Custom Comparers

csharp
public class CustomComparer : IEqualityComparer<string>
{
    public bool Equals(string x, string y)
    {
        return string.Equals(x, y, StringComparison.OrdinalIgnoreCase);
    }
    
    public int GetHashCode(string obj)
    {
        return obj?.ToUpperInvariant().GetHashCode() ?? 0;
    }
}

public class LinqCustomComparers
{
    public static void DemonstrateCustomComparers()
    {
        var words = new[] { "Apple", "apple", "Banana", "banana", "Cherry" };
        
        // Case-insensitive distinct
        var distinctWords = words.Distinct(new CustomComparer());
        
        Console.WriteLine("Distinct words (case-insensitive):");
        foreach (var word in distinctWords)
        {
            Console.WriteLine(word);
        }
    }
}

LINQ with Anonymous Types

csharp
public class LinqAnonymousTypes
{
    public static void DemonstrateAnonymousTypes()
    {
        var people = new[]
        {
            new { FirstName = "John", LastName = "Doe", Age = 30, City = "New York" },
            new { FirstName = "Jane", LastName = "Smith", Age = 25, City = "Los Angeles" },
            new { FirstName = "Bob", LastName = "Johnson", Age = 35, City = "Chicago" }
        };
        
        // Create anonymous type
        var summary = people
            .GroupBy(p => p.City)
            .Select(g => new 
            {
                City = g.Key,
                Count = g.Count(),
                AverageAge = g.Average(p => p.Age),
                Names = g.Select(p => $"{p.FirstName} {p.LastName}").ToList()
            });
        
        foreach (var city in summary)
        {
            Console.WriteLine($"City: {city.City}");
            Console.WriteLine($"  Count: {city.Count}");
            Console.WriteLine($"  Average Age: {city.AverageAge:F1}");
            Console.WriteLine($"  Names: {string.Join(", ", city.Names)}");
        }
    }
}

LINQ to XML

XML Querying with LINQ

csharp
using System.Xml.Linq;

public class LinqToXml
{
    public static void DemonstrateXmlQuerying()
    {
        // Create XML document
        var xml = XElement.Parse(@"
            <books>
                <book id='1' category='Fiction'>
                    <title>The Great Gatsby</title>
                    <author>F. Scott Fitzgerald</author>
                    <price>12.99</price>
                </book>
                <book id='2' category='Non-Fiction'>
                    <title>Thinking, Fast and Slow</title>
                    <author>Daniel Kahneman</author>
                    <price>16.99</price>
                </book>
                <book id='3' category='Fiction'>
                    <title>1984</title>
                    <author>George Orwell</author>
                    <price>14.99</price>
                </book>
            </books>");
        
        // Query XML with LINQ
        var fictionBooks = from book in xml.Elements("book")
                         where (string)book.Attribute("category") == "Fiction"
                         select new
                         {
                             Title = (string)book.Element("title"),
                             Author = (string)book.Element("author"),
                             Price = (decimal)book.Element("price"),
                             Id = (int)book.Attribute("id")
                         };
        
        Console.WriteLine("Fiction books:");
        foreach (var book in fictionBooks)
        {
            Console.WriteLine($"{book.Title} by {book.Author} - ${book.Price}");
        }
    }
}

Performance Considerations

Deferred vs Immediate Execution

csharp
public class LinqPerformance
{
    public static void DemonstrateExecution()
    {
        var numbers = Enumerable.Range(1, 1000000);
        
        // Deferred execution - query is not executed yet
        var query = numbers.Where(n => n % 2 == 0)
                        .Select(n => n * 2);
        
        // Immediate execution - query is executed now
        var result = query.ToList();
        
        // Force immediate execution with ToList(), ToArray(), Count(), etc.
        var count = numbers.Count(n => n > 500000);
        
        Console.WriteLine($"Count: {count}");
        Console.WriteLine($"First 5 results: {string.Join(", ", result.Take(5))}");
    }
}

Optimizing LINQ Queries

csharp
public class LinqOptimization
{
    public static void DemonstrateOptimization()
    {
        var largeList = Enumerable.Range(1, 1000000).ToList();
        
        // Bad: Multiple enumeration
        // var count = largeList.Where(n => n > 500000).Count();
        // var sum = largeList.Where(n => n > 500000).Sum();
        
        // Good: Single enumeration
        var filtered = largeList.Where(n => n > 500000);
        var count = filtered.Count();
        var sum = filtered.Sum();
        
        // Better: Use specific operators
        var count2 = largeList.Count(n => n > 500000);
        var sum2 = largeList.Where(n => n > 500000).Sum();
        
        Console.WriteLine($"Count: {count}, Sum: {sum}");
    }
}

Practical Examples

Data Analysis Example

csharp
public class DataAnalysis
{
    public static void AnalyzeSalesData()
    {
        var sales = new[]
        {
            new { Date = DateTime.Parse("2023-01-01"), Product = "Laptop", Amount = 1200, Region = "North" },
            new { Date = DateTime.Parse("2023-01-02"), Product = "Mouse", Amount = 30, Region = "South" },
            new { Date = DateTime.Parse("2023-01-03"), Product = "Laptop", Amount = 1100, Region = "East" },
            new { Date = DateTime.Parse("2023-01-04"), Product = "Keyboard", Amount = 80, Region = "North" },
            new { Date = DateTime.Parse("2023-01-05"), Product = "Mouse", Amount = 35, Region = "West" }
        };
        
        // Sales by region
        var salesByRegion = sales
            .GroupBy(s => s.Region)
            .Select(g => new 
            {
                Region = g.Key,
                TotalSales = g.Sum(s => s.Amount),
                AverageSale = g.Average(s => s.Amount),
                Count = g.Count()
            })
            .OrderByDescending(r => r.TotalSales);
        
        Console.WriteLine("Sales by Region:");
        foreach (var region in salesByRegion)
        {
            Console.WriteLine($"{region.Region}: Total=${region.TotalSales:N2}, " +
                          $"Avg=${region.AverageSale:N2}, Count={region.Count}");
        }
        
        // Sales by product
        var salesByProduct = sales
            .GroupBy(s => s.Product)
            .Select(g => new 
            {
                Product = g.Key,
                TotalRevenue = g.Sum(s => s.Amount),
                UnitsSold = g.Count(),
                AveragePrice = g.Average(s => s.Amount)
            });
        
        Console.WriteLine("\nSales by Product:");
        foreach (var product in salesByProduct)
        {
            Console.WriteLine($"{product.Product}: Revenue=${product.TotalRevenue:N2}, " +
                          $"Units={product.UnitsSold}, Avg Price=${product.AveragePrice:N2}");
        }
    }
}

Report Generation

csharp
public class ReportGenerator
{
    public static void GenerateEmployeeReport()
    {
        var employees = new[]
        {
            new { Name = "Alice", Department = "IT", Salary = 60000, HireDate = DateTime.Parse("2020-01-15") },
            new { Name = "Bob", Department = "HR", Salary = 50000, HireDate = DateTime.Parse("2019-03-20") },
            new { Name = "Charlie", Department = "IT", Salary = 65000, HireDate = DateTime.Parse("2021-06-10") },
            new { Name = "David", Department = "Finance", Salary = 55000, HireDate = DateTime.Parse("2018-11-05") }
        };
        
        // Department summary
        var departmentSummary = employees
            .GroupBy(e => e.Department)
            .Select(g => new 
            {
                Department = g.Key,
                EmployeeCount = g.Count(),
                TotalSalary = g.Sum(e => e.Salary),
                AverageSalary = g.Average(e => e.Salary),
                NewestHire = g.Max(e => e.HireDate),
                Employees = g.OrderBy(e => e.Name).ToList()
            });
        
        Console.WriteLine("=== Employee Report ===");
        foreach (var dept in departmentSummary)
        {
            Console.WriteLine($"\nDepartment: {dept.Department}");
            Console.WriteLine($"Employees: {dept.EmployeeCount}");
            Console.WriteLine($"Total Salary: ${dept.TotalSalary:N2}");
            Console.WriteLine($"Average Salary: ${dept.AverageSalary:N2}");
            Console.WriteLine($"Newest Hire: {dept.NewestHire:yyyy-MM-dd}");
            Console.WriteLine("Employees:");
            
            foreach (var emp in dept.Employees)
            {
                int yearsOfService = (int)((DateTime.Now - emp.HireDate).TotalDays / 365);
                Console.WriteLine($"  {emp.Name} - {yearsOfService} years");
            }
        }
    }
}

Best Practices

LINQ Guidelines

csharp
// Good: Use appropriate LINQ methods
var result = data.Where(x => x.IsValid)
               .OrderBy(x => x.Name)
               .Take(10);

// Bad: Complex nested queries
var badResult = data.Where(x => 
    data.Where(y => y.Id == x.ParentId)
         .Select(y => y.Name)
         .FirstOrDefault() == "Expected");

// Good: Break complex queries into steps
var parentIds = data.Where(x => x.IsValid).Select(x => x.ParentId);
var parentNames = data.Where(x => parentIds.Contains(x.Id))
                   .ToDictionary(x => x.Id, x => x.Name);

Performance Tips

csharp
// Good: Use specific operators when possible
var count = collection.Count(x => x.IsActive);

// Bad: Use Where().Count() when Count() with predicate exists
var badCount = collection.Where(x => x.IsActive).Count();

// Good: Use AsEnumerable() for database queries
var dbResults = dbContext.Products
    .Where(p => p.Price > 100)
    .AsEnumerable()
    .Select(p => new { p.Name, FormattedPrice = $"${p.Price:N2}" });

Summary

In this chapter, you learned:

  • LINQ query syntax vs method syntax
  • Filtering, projection, ordering, and grouping operators
  • Set operations and aggregation
  • LINQ with different collection types
  • Advanced features like custom comparers
  • LINQ to XML
  • Performance considerations and best practices

LINQ provides a powerful, consistent way to query and manipulate data from various sources. Mastering LINQ will make you more productive when working with collections and data in C#.

Content is for learning and research only.