CompileQuery in EF Core

Morteza JangjooMorteza Jangjoo
3 min read

Entity Framework Core (EF Core) is a powerful ORM (Object-Relational Mapper) for .NET developers. While it provides great flexibility and ease of use, sometimes performance becomes a concern — especially when executing the same LINQ queries repeatedly. One underutilized but powerful feature that addresses this issue is compiled queries using EF.CompileQuery.

In this article, we'll explore how EF.CompileQuery works, when to use it, and how it can help you improve performance in high-traffic or performance-critical applications.


What Is EF.CompileQuery?

Every time you execute a LINQ query in EF Core, it gets parsed, translated, and compiled into SQL. This process involves several steps and can be relatively expensive, especially for complex queries.

EF.CompileQuery allows you to precompile a query once and reuse it multiple times, avoiding the overhead of repeated query compilation.

Syntax

var compiledQuery = EF.CompileQuery(
    (MyDbContext context, int id) =>
        context.Users.FirstOrDefault(u => u.Id == id)
);

// Usage
var user = compiledQuery(dbContext, 1);

When Should You Use Compiled Queries?

Compiled queries are most effective when:

  • The same query is executed frequently with different parameters.

  • The query is performance-critical, and latency matters.

  • The query is relatively complex, so compilation overhead is non-trivial.

For example: fetching user profiles by ID, checking login credentials, retrieving config settings — these are common use-cases.


Limitations

While compiled queries are powerful, they come with a few limitations:

  • No async support with EF.CompileQuery (use EF.CompileAsyncQuery instead).

  • Less flexible than dynamic LINQ queries.

  • You cannot use them for queries that change structure dynamically.


Async Variant: EF.CompileAsyncQuery

EF Core also supports asynchronous compiled queries:

var compiledAsyncQuery = EF.CompileAsyncQuery(
    (MyDbContext context, int id) =>
        context.Users.FirstOrDefault(u => u.Id == id)
);

// Usage
var user = await compiledAsyncQuery(dbContext, 1);

This is ideal in ASP.NET Core applications where asynchronous I/O is essential for scalability.


Benchmark: Is It Really Faster?

Yes, but it depends on the scenario.

ScenarioNormal QueryCompiled Query
First runHigh latency (compilation)Low (precompiled)
Repeated runsMedium latencyVery low latency

In microbenchmarks, compiled queries can save up to 30-40% on CPU for hot paths.


Best Practices

  • Cache compiled queries as static fields.

  • Use meaningful parameterization.

  • Avoid overusing it; not every query benefits from compilation.

  • Profile before and after to verify impact.


Real-World Example

public static class CompiledQueries
{
    public static readonly Func<MyDbContext, string, User?> GetUserByUsername =
        EF.CompileQuery((MyDbContext context, string username) =>
            context.Users.FirstOrDefault(u => u.Username == username));
}

// Usage
var user = CompiledQueries.GetUserByUsername(context, "morteza");

This pattern is great for queries you call hundreds or thousands of times in an app's lifecycle.


Summary

Compiled queries in EF Core are a powerful optimization for high-performance applications. While they’re not necessary for every situation, using them for hot-path, frequently-used queries can significantly reduce overhead.

TL;DR

  • Use EF.CompileQuery for repeated LINQ queries.

  • Save CPU and reduce latency.

  • Use EF.CompileAsyncQuery for async code.

  • Cache the compiled delegates and reuse them.


💬 If you found this helpful, feel free to share or drop your thoughts in the comments!

I’m Morteza Jangjoo and “Explaining things I wish someone had explained to me”


0
Subscribe to my newsletter

Read articles from Morteza Jangjoo directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Morteza Jangjoo
Morteza Jangjoo