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
OrderByandOrderByDescending - 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.
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.
The Problem: Traditional Loops
// 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
using System.Linq;
// Same result, one elegant line
var affordable = products
.Where(p => p.Price < 100m)
.OrderBy(p => p.Price);
// Clear, concise, expressive!
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
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
// 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
// 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
// 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
// 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
// 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
LINQ queries don't execute immediately! They execute when you iterate (foreach) or
materialize (ToList(), ToArray(), Count()).
This is called deferred execution.
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
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}");
}
}
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:
Selectprojects to new shapes - Ordering:
OrderBy,ThenBy - Aggregation:
Count,Sum,Average,Min,Max - LINQ makes code more readable, maintainable, and expressive
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.