Backend Systemsbeginner
LINQ Fundamentals — Query Syntax vs Method Syntax
Master LINQ in C#: filtering, transforming, and aggregating collections with Where, Select, GroupBy, Join, and more. Query syntax vs method syntax explained.
Asma HafeezApril 17, 20264 min read
csharplinqdotnetcollectionsfunctional
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
Enjoyed this article?
Explore the Backend Systems learning path for more.
Found this helpful?
Leave a comment
Have a question, correction, or just found this helpful? Leave a note below.