LINQ in C# — Complete Guide · Lesson 1 of 1
LINQ Fundamentals — Query Syntax vs Method Syntax
LINQ Fundamentals
LINQ (Language Integrated Query) lets you query any collection using a consistent syntax. It works on in-memory lists, databases (via EF Core), XML, and more.
Two Syntaxes — Same Result
C#
var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// Query syntax (SQL-like)
var evens1 =
from n in numbers
where n % 2 == 0
select n;
// Method syntax (fluent)
var evens2 = numbers
.Where(n => n % 2 == 0);
// Both produce the same result: [2, 4, 6, 8, 10]Method syntax is more common in C# codebases — you'll see it in EF Core and most real code.
Filtering — Where
C#
var products = GetProducts();
var expensive = products.Where(p => p.Price > 100);
var inStock = products.Where(p => p.Stock > 0 && p.IsActive);
// Chain multiple conditions
var featured = products
.Where(p => p.Price > 50)
.Where(p => p.Category == "Electronics");Transforming — Select
C#
// Project to a different type
var names = products.Select(p => p.Name); // IEnumerable<string>
var prices = products.Select(p => p.Price); // IEnumerable<decimal>
// Project to anonymous type
var summary = products.Select(p => new {
p.Name,
p.Price,
Discounted = p.Price * 0.9m
});
// Project to a named record/class
var dtos = products.Select(p => new ProductDto(p.Id, p.Name, p.Price));
// SelectMany — flatten nested collections
var tags = products.SelectMany(p => p.Tags); // all tags from all productsSorting — OrderBy
C#
var sorted = products.OrderBy(p => p.Price); // ascending
var sortedDesc = products.OrderByDescending(p => p.Price); // descending
// Multiple sort keys
var multiSort = products
.OrderBy(p => p.Category)
.ThenByDescending(p => p.Price);Aggregates
C#
int count = products.Count();
int expCnt = products.Count(p => p.Price > 100);
bool any = products.Any(p => p.Price > 500);
bool all = products.All(p => p.IsActive);
decimal total = products.Sum(p => p.Price);
decimal avg = products.Average(p => p.Price);
decimal minPrice = products.Min(p => p.Price);
decimal maxPrice = products.Max(p => p.Price);Grouping — GroupBy
C#
var byCategory = products.GroupBy(p => p.Category);
foreach (var group in byCategory)
{
Console.WriteLine($"{group.Key}: {group.Count()} products");
foreach (var p in group)
Console.WriteLine($" - {p.Name}: {p.Price:C}");
}
// Project groups to a summary
var categorySummary = products
.GroupBy(p => p.Category)
.Select(g => new {
Category = g.Key,
Count = g.Count(),
Total = g.Sum(p => p.Price),
Average = g.Average(p => p.Price)
});Joining
C#
var orders = GetOrders();
var customers = GetCustomers();
// Join orders with customers
var orderDetails = orders.Join(
customers,
order => order.CustomerId, // key from orders
customer => customer.Id, // key from customers
(order, customer) => new { // result selector
OrderId = order.Id,
Customer = customer.Name,
Total = order.Total
});
// Group join (left join — all orders, even without customer match)
var leftJoin = orders.GroupJoin(
customers,
o => o.CustomerId,
c => c.Id,
(order, matchedCustomers) => new {
order.Id,
CustomerName = matchedCustomers.FirstOrDefault()?.Name ?? "Unknown"
});First, Single, and Find
C#
var product = products.First(); // first element — throws if empty
var product2 = products.FirstOrDefault(); // first or null
var product3 = products.FirstOrDefault(p => p.Id == 5); // first matching or null
var single = products.Single(p => p.Id == 5); // exactly one — throws if 0 or 2+
var single2 = products.SingleOrDefault(p => p.Id == 5); // one or null — throws if 2+
// Take and Skip (pagination)
var page1 = products.Skip(0).Take(10);
var page2 = products.Skip(10).Take(10);Distinct, Union, Intersect
C#
var ids1 = new[] { 1, 2, 3, 4 };
var ids2 = new[] { 3, 4, 5, 6 };
var union = ids1.Union(ids2); // {1,2,3,4,5,6}
var intersect = ids1.Intersect(ids2); // {3,4}
var except = ids1.Except(ids2); // {1,2}
// Distinct on complex objects requires IEqualityComparer or DistinctBy
var unique = products.DistinctBy(p => p.Category);ToList, ToArray, ToDictionary
LINQ is lazy — it executes only when iterated. Call ToList() to materialize.
C#
// Materialized
List<Product> list = products.Where(p => p.Price > 50).ToList();
Product[] arr = products.OrderBy(p => p.Name).ToArray();
// Dictionary
Dictionary<int, Product> byId = products.ToDictionary(p => p.Id);
Product found = byId[5];
// Lookup (one-to-many)
ILookup<string, Product> byCat = products.ToLookup(p => p.Category);
var electronics = byCat["Electronics"];Key Takeaways
- LINQ is lazy by default — call
.ToList()or.ToArray()to execute and cache the result - Method syntax (fluent) is more common than query syntax in modern C# code
- Use
FirstOrDefaultoverFirstunless an empty result is a bug GroupBy+Selectreplaces most SQLGROUP BY+ aggregate patterns- LINQ works on any
IEnumerable<T>— lists, arrays, EF queries, and custom collections