SQL Server Execution Plans: What EF Core Developers Should Actually Look At

September 26, 2025 · 8 min

You’re looking at a SQL Server execution plan that looks like a spider web of boxes and arrows. Your EF Core query takes 800ms, but you have no idea where to start optimizing.

Execution plans contain overwhelming detail, but EF Core developers only need to focus on specific elements. Here’s what actually matters for optimizing your LINQ queries.

Getting Execution Plans from EF Core

First, you need to capture the SQL that EF Core generates:

// Enable query logging to see generated SQL
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder.UseSqlServer(connectionString)
        .LogTo(Console.WriteLine, LogLevel.Information)
        .EnableSensitiveDataLogging(); // Shows parameter values
}

Run your EF Core query and copy the logged SQL:

var expensiveOrders = await context.Orders
    .Include(o => o.Customer)
    .Include(o => o.OrderItems)
        .ThenInclude(oi => oi.Product)
    .Where(o => o.OrderDate >= DateTime.Now.AddDays(-30))
    .ToListAsync();

EF Core generates SQL like this:

SELECT [o].[Id], [o].[CustomerId], [o].[OrderDate], [o].[Total],
       [c].[Id], [c].[Name], [c].[Email],
       [o0].[Id], [o0].[OrderId], [o0].[ProductId], [o0].[Quantity],
       [p].[Id], [p].[Name], [p].[Price]
FROM [Orders] AS [o]
INNER JOIN [Customers] AS [c] ON [o].[CustomerId] = [c].[Id]
LEFT JOIN [OrderItems] AS [o0] ON [o].[Id] = [o0].[OrderId]
LEFT JOIN [Products] AS [p] ON [o0].[ProductId] = [p].[Id]
WHERE [o …

Read more