Speeding up LINQ to SQL

There are a number of ways to improve the performance of LINQ to SQL queries.

1. Cache the Mapping Source

When executing large LINQ to SQL queries (those involving many tables), LINQ to SQL's object relational mapper incurs a significant overhead in building the internal metamodel. The metamodel describes how your entity classes map to the underlying tables and columns, and is built automatically via reflection (assuming you're using attributes rather than an XML file, to map tables and columns). Ordinarily, this overhead is incurred every time you instantiate a DataContext, which is not great for performance. You can avoid this by manually specifying a single static MappingSource object for all typed DataContext instances as follows:

public class MyDataContext : DataContext
{
   static MappingSource _sharedMappingSource = new AttributeMappingSource();

   public MyDataContext (IDbConnection cx) : base (cx, _sharedMappingSource) { }
   public MyDataContext (string cxString) : base (cxString, _sharedMappingSource) { }
}

You might wonder what happens in a multithreaded environment. What if two threads (e.g., from two different client requests) instantiate their own instances of MyDataContext and start executing queries at the same time?  Fortunately, everything works correctly because MetaModel instances are thread-safe. I've used this technique in a large CRM application that uses LINQ to SQL almost entirely for data access: the system has over 500 users and has been running without trouble since early 2008. (Without this optimization, some queries were simply too slow for LINQ to SQL to be viable).

Note that although MetaModel (MappingSource) instances are thread-safe, the same is not true for DataContext instances. You cannot share a single DataContext instance between multiple threads, or things will go terribly wrong! (And not that you'd want to: doing so would mean taking on unpleasant concurrency issues that are otherwise handled very well by SQL Server itself, through its transaction isolation level semantics).

2. Use Compiled Queries

Ordinarily, LINQ to SQL must translate LINQ queries to SQL every time a query executes; this involves recursing the expression tree that makes up the query in several stages. It sounds worse than it is: the computation cost is not enormous in the overall scheme of things (certainly not as big as the cost of building an AttributeMappingSource when lots of entities are involved). Nonetheless, you can avoid paying the price on each query execution by precompiling the query using the CompiledQuery class.

Here's an example that you can paste directly into LINQPad:

var cc = CompiledQuery.Compile ((TypedDataContext dc, decimal minPrice) =>    
   from c in Customers
   where c.Purchases.Any (p => p.Price > minPrice)
   select c
);

cc (this, 100).Dump ("Customers who spend more than $100");
cc (this, 1000).Dump ("Customers who spend more than $1000");

3. Use Table-Valued Functions Where Necessary

Finally, there are some queries for which LINQ to SQL producesless-than-ideal SQL. Furthermore, some SQL queries need optimization hints to get the best performance, and optimization hints are impossible with LINQ to SQL alone. A compromise in these situations is to encapsulate the difficult part of the query in a table-valued function, and then map this function into your typed DataContext class. You can then run LINQ queries over this function, and the queries will be translated to SQL and execute on the server. So, in effect, you're mix SQL and LINQ to SQL in the same query.
 

 

© 2007-2012, Joe Albahari, Ben Albahari and O'Reilly Media, Inc. All rights reserved

C# 5.0 in a Nutshell
Buy from amazon.com Available now