NashTech Blog

Table of Contents

5 LINQ Tricks You Should Know

Language Integrated Query (LINQ) is one of the most expressive features in C#.
It helps you filter, transform, and shape data with clean, functional syntax.

But many developers only scratch the surface — using only .Where(), .Select(), and .ToList().

Let’s level up with 5 practical LINQ tricks that make your C# code more powerful, readable, and efficient.

1. Grouping by Multiple Keys

Most developers know `.GroupBy()` for one property — but you can group by multiple keys easily.

var orders = new[]
{
    new { Customer = "Alice", Year = 2024, Amount = 120 },
    new { Customer = "Alice", Year = 2024, Amount = 80 },
    new { Customer = "Bob",   Year = 2025, Amount = 200 },
    new { Customer = "Bob",   Year = 2024, Amount = 150 }
};

var grouped = orders
    .GroupBy(o => new { o.Customer, o.Year })
    .Select(g => new
    {
        g.Key.Customer,
        g.Key.Year,
        Total = g.Sum(x => x.Amount)
    });

foreach (var g in grouped)
    Console.WriteLine($"{g.Customer} ({g.Year}): {g.Total}");

Why it’s useful:
This pattern works beautifully in reporting, dashboards, or data exports where you need aggregation by multiple dimensions.

2. Using ToLookup() for Instant Grouped Dictionaries

`ToLookup()` is like `GroupBy()` but materialized — it gives you a **read-only dictionary** style collection.

var orders = new[]
{
    new { Customer = "Alice", Product = "Book", Amount = 120 },
    new { Customer = "Alice", Product = "Pen", Amount = 80 },
    new { Customer = "Bob",   Product = "Notebook", Amount = 200 },
    new { Customer = "Bob",   Product = "Pen", Amount = 150 }
};

var lookup = orders.ToLookup(o => o.Customer);

foreach (var customer in lookup)
{
    Console.WriteLine($"{customer.Key} has {customer.Count()} orders");
}

Now you can quickly access all orders for `”Alice”` via:

var aliceOrders = lookup["Alice"];

Key difference from GroupBy():

  • GroupBy() returns a deferred query
  • ToLookup() immediately materializes into a lookup table
  • Use GroupBy() when you need deferred execution
  • Use ToLookup() when you need multiple lookups by key

Why it’s powerful:

  • Fast lookups by key
  • No need to re-group each time
  • Perfect for in-memory joins

3. Joining Collections with Custom Keys

You can join two lists on any key — similar to SQL.

var customers = new[]
{
    new { Id = 1, Name = "Alice" },
    new { Id = 2, Name = "Bob" }
};

var ordersList = new[]
{
    new { CustomerId = 1, Product = "Book" },
    new { CustomerId = 2, Product = "Pen" },
    new { CustomerId = 2, Product = "Notebook" }
};

var joined = from c in customers
             join o in ordersList on c.Id equals o.CustomerId
             select new { c.Name, o.Product };

foreach (var item in joined)
    Console.WriteLine($"{item.Name} ordered {item.Product}");

Pro tip:
You can also use .GroupJoin() for left joins (include customers with no orders).

4. Avoiding Multiple Enumerations with .ToList() or .ToArray()

LINQ queries are deferred — they don’t execute until you enumerate them.

That means this code runs the same query *twice*:

var expensiveQuery = orders.Where(o => o.Amount > 100);

Console.WriteLine(expensiveQuery.Count());
Console.WriteLine(expensiveQuery.FirstOrDefault()?.Amount ?? 0);

Note: Using First() can throw if the collection is empty. Use FirstOrDefault() for safety, or check Count() > 0 first.

Fix: materialize the query once:

var results = orders.Where(o => o.Amount > 100).ToList();
Console.WriteLine(results.Count);
Console.WriteLine(results.First().Amount);

💡 Why it matters:
Each enumeration re-runs the query — costly for EF Core or external data sources.

5. Using Aggregate() for Custom Reductions

When `Sum()`, `Min()`, or `Max()` aren’t enough — `Aggregate()` lets you define custom accumulation logic.

var numbers = new[] { 2, 3, 4 };

var product = numbers.Aggregate(1, (acc, x) => acc * x);
Console.WriteLine(product); // 24

You can even use it for string joins:

var joined = numbers.Aggregate("Result:", (acc, x) => $"{acc} {x}");
Console.WriteLine(joined); // Result: 2 3 4

Use cases:

  • Combining custom objects
  • Building CSV strings
  • Complex data reductions

Bonus: Debugging LINQ with .Dump() or .ToQueryString()

If you use LINQ to Entities (EF Core), you can inspect the actual SQL with:

var query = _context.Orders.Where(o => o.Amount > 100);
Console.WriteLine(query.ToQueryString());

And if you use LINQPad, try `.Dump()` to visualize results instantly.  

This is great for debugging and performance analysis.

Summary

Trick Description
1. Group by multiple keys Aggregate by complex dimensions
2. ToLookup() Create fast lookup dictionaries
3. Custom joins SQL-like join between collections
4. Materialize results Avoid multiple enumerations
5. Aggregate() Custom accumulation logic

Final Thoughts

LINQ isn’t just for filtering — it’s a full mini-language for data transformation inside C#.
Mastering these small tricks helps you write cleaner, faster, and more maintainable code — especially in enterprise .NET projects.

Picture of Hoc Nguyen Thai

Hoc Nguyen Thai

Leave a Comment

Your email address will not be published. Required fields are marked *

Suggested Article

Scroll to Top