EF Core Compiled Queries: When They’re Worth It in High-Throughput Apps

September 25, 2025 · 8 min

Your API handles 50,000 requests per second, and each request executes the same customer lookup query. Profiling shows 15% of CPU time is spent translating LINQ expressions to SQL, not actually running the database query.

This is where EF Core compiled queries shine. They pre-compile the LINQ-to-SQL translation, eliminating that CPU overhead for frequently executed queries.

But compiled queries aren’t a magic performance solution. They have specific use cases and trade-offs that many developers misunderstand.

How EF Core Query Compilation Works

Every time you execute a LINQ query, EF Core goes through several steps:

  1. Parse the LINQ expression tree
  2. Translate LINQ to database-specific SQL
  3. Cache the translation result
  4. Execute the SQL query
// Normal query - goes through full pipeline every time
var customer = await context.Customers
    .Where(c => c.Id == customerId)
    .Include(c => c.Orders)
    .FirstOrDefaultAsync();

EF Core caches query plans, but complex LINQ expressions still require parsing and validation on each execution. For simple queries executed thousands of times per second, this overhead becomes significant.

Compiled Query Implementation

Compiled queries pre-compile the LINQ expression, skipping steps 1-2 on subsequent executions:

public static class CompiledQueries
{
    // Compiled query with single parameter
    public static readonly Func<CustomerContext, int, Task<Customer>> GetCustomerById =
        EF.CompileAsyncQuery(( …

Read more

Fix Slow EF Core Queries with Projections

September 10, 2025 · 6 min

I’ve seen this happen a dozen times. An API endpoint is dog-slow, taking 2-3 seconds to respond. Everyone blames the network or the database. We check the query execution plan, and it’s fine—the database returns data in 50 milliseconds. So what’s the problem?

The problem is the 900KB of data we’re pulling from the database, serializing, and then throwing away for every single request. The culprit is a silent performance killer in Entity Framework Core: fetching full entities when you only need a few properties.

The Default Behavior is a Trap

It’s easy to write code like this. It’s clean, it’s simple, and it works.

var orders = await _dbContext.Orders
    .Where(o => o.Status == "Active")
    .ToListAsync();

But under the hood, EF Core is trying to be helpful by generating a SELECT * style query. If your Orders table has a few large NVARCHAR(MAX) columns for notes, addresses, or serialized metadata, you’re in for a bad time.

Here’s the SQL it probably generates:

SELECT [o].[Id], [o].[CustomerId], [o].[OrderDate], [o].[Status], 
       [o].[Notes], [o].[ShippingAddress], [o].[BillingAddress],
       [o].[InternalComments], [o].[AuditData], [o].[SerializedMetadata]
FROM [Orders] AS [o]
WHERE [o].[Status] = 'Active'

If your API only needs to display the order ID, customer name, and total, you just paid a heavy price in network I/O and memory allocation for nothing. That SerializedMetadata column alone could be megabytes.

The Fix: Project Only What You Need

The …

Read more