Back to blog
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
Share:𝕏

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 products

Sorting — 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

  1. LINQ is lazy by default — call .ToList() or .ToArray() to execute and cache the result
  2. Method syntax (fluent) is more common than query syntax in modern C# code
  3. Use FirstOrDefault over First unless an empty result is a bug
  4. GroupBy + Select replaces most SQL GROUP BY + aggregate patterns
  5. 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?

Share:𝕏

Leave a comment

Have a question, correction, or just found this helpful? Leave a note below.