Speeding Up LINQ Queries in C#
Common issue when using LINQ with large Datasets
LINQ helps us write queries in a very simple and clean way. But often, when we work with a large amount of datasets, our LINQ queries can become slow.
As an example, if we are using LINQ query in generating a report which shows customer orders. When there are only a few hundred orders, everything works as expected. But when the list grows to thousands, the queries starts taking longer consequently making application slower.
When this happens, we need to optimize our LINQ queries without having to change the approach or using a different method.
Solution
The main reason LINQ queries slow down with large data is because of how they are executed. LINQ often uses deferred execution, which means that it processes data only when the result is requested. This is useful in many scenarios but often leads to unnecessary work.
Also, chaining LINQ operators (like Where
, Select
, OrderBy
) without optimization can cause multiple iterations over making the query execution slower.
To make the LINQ queries faster, we can use the following guidelines:
-
Use efficient filtering and projection early: Applying filters (
Where
) as soon as possible to reduce the data size. -
Avoid repeated enumeration: Save the query result once using methods like
.ToList()
or.ToArray()
if we need to iterate multiple times. -
Use indexed or hashed collections if appropriate: For example, converting data to a
Dictionary
orHashSet
can speed up lookups. -
Avoid complex or unnecessary operations inside queries: For instance, don’t call expensive functions repeatedly inside a query.
-
Consider parallelization: For CPU-heavy queries,
AsParallel()
from PLINQ can help if used carefully.
By applying the above based on our requirements, we can improve the performance of our LINQ queries with large datasets.
As an example consider the following pseudocode for improving LINQ performance:
// Start with the full data collection.Get the original dataset, which might be large.
dataCollection = GetData()
// Apply filtering early to reduce data size and to remove unnecessary items early.
filteredData = dataCollection.Where(condition)
// select only the data fields we actually need
projectedData = filteredData.Select(neededFields)
// Materialize the query into a list to avoid multiple enumerations
resultList = projectedData.ToList()
// Step 5: Use a Dictionary for quick lookup if needed
lookupDictionary = resultList.ToDictionary(keySelector)
// Step 6: Perform any further operations using the optimized collections
finalResult = ProcessUsing(lookupDictionary)
If we have an Order class
public class Order
{
public int OrderId { get; set; }
public int CustomerId { get; set; }
public decimal Amount { get; set; }
public DateTime OrderDate { get; set; }
}
// Assume this method returns a large list of ordersList<Order> GetOrders()
{
// this returns thousands of records
return new List<Order>
{
new Order { OrderId = 1, CustomerId = 101, Amount = 250, OrderDate = DateTime.Today.AddDays(-10) },
new Order { OrderId = 2, CustomerId = 102, Amount = 450, OrderDate = DateTime.Today.AddDays(-5) },
// ... more orders
};
}
void ProcessOrders()
{
// Step 1: Get all orders
var allOrders = GetOrders();
//Filter orders - for example, only orders in the last 30 days
var recentOrders = allOrders.Where(o => o.OrderDate >= DateTime.Today.AddDays(-30));
// Step 3: Project only needed fields (OrderId and Amount)
var projectedOrders = recentOrders.Select(o => new { o.OrderId, o.Amount });
// Step 4: Materialize the query to a list to avoid multiple enumerations
var orderList = projectedOrders.ToList();
// Step 5: Create a dictionary keyed by OrderId for quick lookup
var orderDictionary = orderList.ToDictionary(o => o.OrderId);
// Step 6: Use the dictionary for quick access or further processing
if (orderDictionary.TryGetValue(1, out var order))
{
Console.WriteLine($"Order 1 Amount: {order.Amount}");
}
// Further processing can be done here efficiently
}
Summary
When LINQ queries slow down, it’s usually because of how and when they execute. By applying the above steps of filtering early, projecting only what we need, saving intermediate results, and using the right data structures, we can significantly boost performance without losing LINQ’s expressiveness.
Follow on:

