Solving common problems with Compiled Queries in Linq to Sql for high demand ASP.NET websites

If you are using Linq to SQL, instead of writing regular Linq
Queries, you should be using
Compiled Queries
. if you are building an ASP.NET web
application that’s going to get thousands of hits per hour,
the execution overhead of Linq queries is going to consume too much
CPU and make your site slow. There’s a runtime cost
associated with each and every Linq Query you write. The queries
are parsed and converted to a nice SQL Statement on *every* hit.
It’s not done at compile time because there’s no way to
figure out what you might be sending as the parameters in the
queries during runtime. So, if you have common Linq to Sql
statements like the following one throughout your growing web
application, you are soon going to have scalability nightmares:

var query = from widget in dc.Widgets
where widget.ID == id && widget.PageID == pageId
select widget;

var widget = query.SingleOrDefault();

There’s
a nice blog post by JD Conley
that shows how evil Linq to Sql
queries are:


image

You see how many times SqlVisitor.Visit is called to
convert a Linq Query to its SQL representation? The runtime cost to
convert a Linq query to its SQL Command representation is just way
too high.


Rico Mariani has a very informative performance comparison
of
regular Linq queries vs Compiled Linq queries performance:


image

Compiled Query wins on every case.

So, now you know about the benefits of compiled queries. If you
are building ASP.NET web application that is going to get high
traffic and you have a lot of Linq to Sql queries throughout your
project, you have to go for compiled queries. Compiled Queries are
built for this specific scenario.

In this article, I will show you some steps to convert regular
Linq to Sql queries to their Compiled representation and how to
avoid the dreaded exception “Compiled queries across
DataContexts with different LoadOptions not
supported.”

Here are some step by step instruction on converting a Linq to
Sql query to its compiled form:

First we need to find out all the external decision factors in a
query. It mostly means parameters in the WHERE clause. Say, we are
trying to get a user from aspnet_users table using
Username and Application ID:

Here, we have two external decision factor – one is the
Username and another is the Application ID. So, first think this
way, if you were to wrap this query in a function that will just
return this query as it is, what would you do? You would create a
function that takes the DataContext (dc named here),
then two parameters named userName and applicationID, right?

So, be it. We create one function that returns just this
query:


Converting a LInq Query to a function

Next step is to replace this function with a Func<> representation
that returns the query. This is the hard part. If you haven’t
dealt with Func<> and Lambda
expression before, then I suggest you read this
and
this
and then continue.

So, here’s the delegate representation of the above
function:


Creating a delegate out of Linq Query

Couple of things to note here. I have declared the delegate as
static readonly
because a compiled query is declared only once and reused by all
threads. If you don’t declare Compiled Queries as static,
then you don’t get the performance gain because compiling
queries everytime when needed is even worse than regular Linq
queries.

Then there’s the complex Func> thing. Basically the
generic Func<> is declared to
have three parameters from the GetQuery function and a return
type of IQueryable.
Here the parameter types are specified so that the delegate is
created strongly typed. Func<> allows up to 4
parameters and 1 return type.

Next comes the real business, compiling the query. Now that we
have the query in delegate form, we can pass this to CompiledQuery.Compile function
which compiles the delegate and returns a handle to us. Instead of
directly assigning the lambda expression to the func, we will pass
the expression through the CompiledQuery.Compile
function.


Converting a Linq Query to Compiled Query

Here’s where head starts to spin. This is so hard to read
and maintain. Bear with me. I just wrapped the lambda expression on
the right side inside the CompiledQuery.Compile function.
Basically that’s the only change. Also, when you call
CompiledQuery.Compile<>,
the generic types must match and be in exactly the same order as
the Func<>
declaration.

Fortunately, calling a compiled query is as simple as calling a
function:


Running Compiled Query

There you have it, a lot faster Linq Query execution. The hard
work of converting all your queries into Compiled Query pays off
when you see the performance difference.

Now, there are some challenges to Compiled Queries. Most common
one is, what do you do when you have more than 4 parameters to
supply to a Compiled Query? You can’t declare a Func<> with more than 4
types. Solution is to use a struct to encapsulate all the
parameters. Here’s an example:


Using struct in compiled query as parameter

Calling the query is quite simple:


Calling compiled query with struct parameter

Now to the dreaded challenge of using LoadOptions with Compiled
Query. You will notice that the following code results in an
exception:


Using DataLoadOptions with Compiled Query

The above DataLoadOption runs perfectly
when you use regular Linq Queries. But it does not work with
compiled queries. When you run this code and the query hits the
second time, it produces an exception:

Compiled queries across DataContexts with different
LoadOptions not supported

A compiled query remembers the DataLoadOption once its called.
It does not allow executing the same compiled query with a
different DataLoadOption again. Although
you are creating the same DataLoadOption with the same
LoadWith<>
calls, it still produces exception because it remembers the exact
instance that was used when the compiled query was called for the
first time. Since next call creates a new instance of DataLoadOptions, it does not
match and the exception is thrown. You can read details about the
problem in
this forum post
.

The solution is to use a static DataLoadOption. You cannot
create a local DataLoadOption instance and use
in compiled queries. It needs to be static. Here’s how you
can do it:


image

Basically the idea is to construct a static instance of DataLoadOptions using a static
function. As writing function for every single DataLoadOptions combination is
painful, I created a static delegate here and executed it right on
the declaration line. This is in interesting way to declare a
variable that requires more than one statement to prepare it.

Using this option is very simple:


image

Now you can use DataLoadOptions with compiled
queries.


kick it on DotNetKicks.com

20 thoughts on “Solving common problems with Compiled Queries in Linq to Sql for high demand ASP.NET websites”

  1. I've applied this on LINQ to Entites, but Omar I have a question! Shall I use this will frequently used queries?! what if I decided to be smart and complie each Query? what would be the concequences?

  2. Isn't easier and faster to create a store procedure and call the SP through LINQ?

  3. So much code for such little reward.

    It seems a lot easier in many respects to go back to basics, as ive had my fingers burnt a few times with things like this in live situations.

    LINQ to SQL requires a huge learning investment just to get it performing at an acceptable performance level.

  4. You must make all DataLoadOptions static even if don't use compiled queries. It is a deep dive secret of L2S I've found hard way.

    L2S cache last 10 entity materializers and will reuse them but only if DataContext has has the same (or empty) instance of DataLoadOptions assigned. otherwise it will compile it EACH time spending cpu and memory.

    Look at System.Data.Linq.SqlClient.ObjectReaderCompiler.Compile(SqlExpression, Type) : IObjectReaderFactory

    and System.Data.Linq.SqlClient.ObjectReaderCompiler.maxReaderCacheSize : Int32

  5. I was planning on changing my dal to use linq to sql to make my life creating queries in the bll abit easier and less painful but the performance hit has put me off completely. I appreciate the workaround but it's more work than going back to basics. I hope this will be resolved in .net 4.

  6. Nice post.

    I would welcome any performance improvement in linq to sql. However, there is some stories floating around saying linq to sql is dead.

    BTW, is it possible to use pure code instead of posting the screen shots?

  7. Thanks for the info on how to use CompiledQueries with LoadWith! I've been trying to figure that out for a while now and had just about concluded it couldn't be done.

    Now I've just got to update my app to use static instances . . .

  8. I too have been searching for a way to use the DataLoadOptions w/ compiled queries, so thanks for the post!

    It's only natural that you'd want to use DataLoadOptions w/ a compiled query, but it took me way to long to find this answer.

    Is there a way that doesn't require an additional line of code to use it each time you want to use the compiled query?

    Couldn't the static DataLoadOptions somehow be part of the initialization of the compiled query?

  9. re: Solving common problems with Compiled Queries in Linq to Sql for high demand ASP.NET websites says:

    Hi daniel,

    “in fact if they are they become less efficient that dynamic queries. “

    Compiled Queries are definitely more efficient than dynamic queries in terms of execution. They are compiled, faster and does not consume memory like dynamic queries does on every execution.

    I believe you wanted to say compiled queries become less reusable than dynamic queries.

    Compiled queries are like fixed queries. You have to build the whole query and just call it. I do not know a way to join a compiled query with another compiled query or dynamic query. In fact I am curious how you join a dynamic query with another one.

  10. re: Solving common problems with Compiled Queries in Linq to Sql for high demand ASP.NET websites says:

    Omar, great article. I had hoped this would solve some of the speed issues that we have been facing, however was disapointed to find that the compiled queries couldn't be reused further, in fact if they are they become less efficient that dynamic queries.

    Eg: I tried to join another table onto a compiled query, but this blew out the query. However it looks like I can refine the query without losing performance so long as I'm not changing the structure by adding more tables etc.

    Are there any way to join onto compiled queries without losing the performance gain?

  11. re: Solving common problems with Compiled Queries in Linq to Sql for high demand ASP.NET websites says:

    Hi Omar, thanks for that.

    I guess because the Compiled Queries return an IQueryable I was expecting to be able to reuse it like you do a dynamic query:

    IQueryable query = …(ExecuteCompiledQuery)

    and then be able to go:

    int totalCount = query.Count();

    List results = query.Skip(10).Take(10).ToList();

    But having looked into it, I can see that the query is actually executed immediately on the SQL box when you call the Compiled Query.

    So I guess I would need a seperate compiled query for the count and then another compiled query which has “skip” and “take” paramters to actually get the search result range I'm after… The problem with this is that I have to write and maintain duplicate code.

  12. re: Solving common problems with Compiled Queries in Linq to Sql for high demand ASP.NET websites says:

    Hi, I tried to use this but it failed.

    Im using an object of type Expression> in may where clause. like this:

    internal static readonly

    Func>,IQueryable>

    Compiled_GetParameters = CompiledQuery.Compile>, IQueryable>(

    (db,predicate) => db.MswToParameter.Where(predicate).Select(m => m.Parameter).Distinct());

    When called I get an NotSupportedException: Unsupported overload used for query operator 'Where'.

    The query works as a dynamic linq query.

    What have I missed? Where shall I begin to search for the error?

  13. re: Solving common problems with Compiled Queries in Linq to Sql for high demand ASP.NET websites says:

    Hi, very good explaination.

    But I missed one thing. The comparison to stored procedures in SQL Server. They can be executed by LINQ.

    Finally I would say this is just an opinion when it's not possible to add a stored procedure.

  14. re: Solving common problems with Compiled Queries in Linq to Sql for high demand ASP.NET websites says:

    Hi,

    I don't get at all how you are “passing” the delegate to the compiled query. The delegate is called GetUserFromUserName, right? There is no reference to this function in the Compiled_GetUserFromUserName. When I mimic this code I get an error stating “cannot convert method group SingleOrDefault to non-delegate type System.Linq.IQueryable”. What am I missing?

  15. Thank you for submitting this cool story – Trackback from PimpThisBlog.com

  16. Hi,

    Very clear walk through!

    Using LINQ to SQL, I need to change the database schema at runtime. Table names remain the same as in design time, just the schema will be different; i.e. design time configuration is like this [Table(Name=”schema1.table”)], while at runtime I need to use “table” from schema2, as in [Table(Name=”schema2.table”)].

    If I use dynamic mapping as here: http://msdn.microsoft.com/en-us/bb546173.aspx, would I still benefit from the performance of the compiled queries?

    To be more concise I plan to instantiate the DataContext multiple times and pass a different metadata each time I instantiate it. Would the query be recompiled each time the data context is instantiated with different metadata? I guess re-instantiating the context should make no difference to the compiled query, but as I haven’t done myself any tests, but maybe someone knows already the answer?

    Thanks in advance!

  17. Hi!
    How can i compile query like this (different number of params):

    IQueryable q = …
    if (orderId != null)
    q = q.Where(q => q.OrderID == orderId);
    if (userId != null)
    q = q.Where(q => q.UserId == userId);

    see the problem, if orderId == null then I don’t need that where part. Is the only solution writing code like this:

    q = q.Where(q => (q.UserId == userId || userId == null) && (q.OrderId == orderId || orderId == null));

    but that is less efficient because of not needed comparisson if userId or orderId is null. Or that isn’t much? Pls enlighten me:)