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 queryToLookup()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.