EF split query lab
By Milan Jovanović · EF 10 and EF 11 · PostgreSQL 18 · BenchmarkDotNet
Seed a PostgreSQL 18 database with 20,000 orders and run the split queries from the article on EF 10 and EF 11. One project builds against both versions, so you can compare the SQL each one sends and benchmark them side by side.
1. Download the files
You need Docker and both the .NET 10 SDK and the .NET 11 SDK. Download and extract the ZIP, then open the ef-split-query folder inside. The first run restores the packages listed in Benchmarks/Benchmarks.csproj.
Includes all 14 files below. You can also preview or download each file individually.
- Download
README.md
Setup, commands, and what to compare
Preview contents of README.md
# EF split query lab Companion to [EF 11 Drops a Redundant Join From Split Queries](https://milanjovanovic.tech/blog/ef-core-11-split-query-redundant-join). One benchmark project builds the same queries against EF Core 10 on .NET 10 and EF Core 11 on .NET 11. You can dump the SQL each version sends for a split query and benchmark both against the same PostgreSQL 18 database. ## What you need - Docker - The .NET 10 SDK and the .NET 11 SDK ## Run it ```sh docker compose up -d --wait cd Benchmarks dotnet run -c Release -f net11.0 -- seed dotnet run -c Release -f net10.0 -- dump-sql sql-ef10.txt dotnet run -c Release -f net11.0 -- dump-sql sql-ef11.txt dotnet run -c Release -f net11.0 -- --filter '*' ``` - `seed` recreates the schema and inserts 1,000 customers, 500 products, 20,000 orders with 5 lines and 1 to 2 payments each, and about 30,000 addresses, from a fixed random seed. - `dump-sql` runs every query once and writes the SQL EF Core sent. The target framework picks the EF Core version. - The last command runs four benchmarks on both runtimes with BenchmarkDotNet. It takes several minutes. `docker compose down -v` removes the container and its data. ## What is here - `docker-compose.yml`: PostgreSQL 18 on `127.0.0.1:5438`. - `src/Model.cs` and `src/AppDbContext.cs`: `Order` with a required `Customer` and `ShippingAddress`, an optional `BillingAddress` and `Carrier`, and the `Lines` and `Payments` collections. - `src/Queries.cs`: the paged query and the date-range query, each with one reference and with four. - `src/Seeder.cs` and `src/SqlDump.cs`: the data and the SQL dump. - `Benchmarks/`: the project file with one EF Core package per target framework, the two jobs, and the four benchmarks. - `output/`: my SQL dumps for both versions and my BenchmarkDotNet results. ## What to compare - `output/sql-ef10.txt` and `output/sql-ef11.txt` are my dumps. Your statements should match them, the timestamps will not. - `output/benchmarkdotnet.md` is my run. Your timings will differ, the ratios between EF Core 10 and EF Core 11 should be close. - Download
docker-compose.yml
PostgreSQL 18 container on port 5438
Preview contents of docker-compose.yml
services: postgres: image: postgres:18 environment: POSTGRES_USER: mnw220 POSTGRES_PASSWORD: mnw220 POSTGRES_DB: orders ports: - "127.0.0.1:5438:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U mnw220 -d orders"] interval: 2s timeout: 3s retries: 30 - Download
src/Queries.cs
The paged and date-range split queries, with one reference and with four
Preview contents of src/Queries.cs
using Microsoft.EntityFrameworkCore; namespace Api; public static class OrderQueries { public const int PageSize = 20; // One page of a customer's orders with every navigation the order page needs. public static Task<List<Order>> PageAsync(AppDbContext db, int customerId, int page, CancellationToken ct) => db.Orders .Where(o => o.CustomerId == customerId) .OrderByDescending(o => o.OrderedAt) .ThenBy(o => o.Id) .Skip(page * PageSize) .Take(PageSize) .Include(o => o.Customer) .Include(o => o.Lines) .ThenInclude(l => l.Product) .Include(o => o.Payments) .AsSplitQuery() .AsNoTracking() .ToListAsync(ct); // Every order in a date range, the shape a report or an export loads. public static Task<List<Order>> RangeAsync(AppDbContext db, DateTime from, DateTime to, CancellationToken ct) => db.Orders .Where(o => o.OrderedAt >= from && o.OrderedAt < to) .Include(o => o.Customer) .Include(o => o.Lines) .ThenInclude(l => l.Product) .Include(o => o.Payments) .AsSplitQuery() .AsNoTracking() .ToListAsync(ct); // The same page with four references on the order instead of one: // two required (Customer, ShippingAddress) and two optional (BillingAddress, Carrier). public static Task<List<Order>> PageWithReferencesAsync(AppDbContext db, int customerId, int page, CancellationToken ct) => db.Orders .Where(o => o.CustomerId == customerId) .OrderByDescending(o => o.OrderedAt) .ThenBy(o => o.Id) .Skip(page * PageSize) .Take(PageSize) .Include(o => o.Customer) .Include(o => o.ShippingAddress) .Include(o => o.BillingAddress) .Include(o => o.Carrier) .Include(o => o.Lines) .ThenInclude(l => l.Product) .Include(o => o.Payments) .AsSplitQuery() .AsNoTracking() .ToListAsync(ct); // The same report with the four references. public static Task<List<Order>> RangeWithReferencesAsync(AppDbContext db, DateTime from, DateTime to, CancellationToken ct) => db.Orders .Where(o => o.OrderedAt >= from && o.OrderedAt < to) .Include(o => o.Customer) .Include(o => o.ShippingAddress) .Include(o => o.BillingAddress) .Include(o => o.Carrier) .Include(o => o.Lines) .ThenInclude(l => l.Product) .Include(o => o.Payments) .AsSplitQuery() .AsNoTracking() .ToListAsync(ct); } - Download
src/Model.cs
Order with four references and two collections
Preview contents of src/Model.cs
namespace Api; public sealed class Customer { public int Id { get; set; } public required string Name { get; set; } public required string Email { get; set; } public required string Country { get; set; } } public sealed class Product { public int Id { get; set; } public required string Sku { get; set; } public required string Name { get; set; } public decimal Price { get; set; } } public sealed class Address { public int Id { get; set; } public required string Street { get; set; } public required string City { get; set; } public required string PostalCode { get; set; } public required string Country { get; set; } } public sealed class Carrier { public int Id { get; set; } public required string Name { get; set; } } public sealed class Order { public int Id { get; set; } public int CustomerId { get; set; } public Customer Customer { get; set; } = null!; public DateTime OrderedAt { get; set; } public required string Status { get; set; } public int ShippingAddressId { get; set; } public Address ShippingAddress { get; set; } = null!; public int? BillingAddressId { get; set; } public Address? BillingAddress { get; set; } public int? CarrierId { get; set; } public Carrier? Carrier { get; set; } public List<OrderLine> Lines { get; set; } = []; public List<Payment> Payments { get; set; } = []; } public sealed class OrderLine { public int Id { get; set; } public int OrderId { get; set; } public int ProductId { get; set; } public Product Product { get; set; } = null!; public int Quantity { get; set; } public decimal UnitPrice { get; set; } } public sealed class Payment { public int Id { get; set; } public int OrderId { get; set; } public decimal Amount { get; set; } public DateTime PaidAt { get; set; } public required string Method { get; set; } } - Download
src/AppDbContext.cs
Relationships and indexes
Preview contents of src/AppDbContext.cs
using Microsoft.EntityFrameworkCore; namespace Api; public sealed class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options) { public DbSet<Customer> Customers => Set<Customer>(); public DbSet<Product> Products => Set<Product>(); public DbSet<Address> Addresses => Set<Address>(); public DbSet<Carrier> Carriers => Set<Carrier>(); public DbSet<Order> Orders => Set<Order>(); public DbSet<OrderLine> OrderLines => Set<OrderLine>(); public DbSet<Payment> Payments => Set<Payment>(); protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Customer>(e => { e.Property(c => c.Name).HasMaxLength(100); e.Property(c => c.Email).HasMaxLength(200); e.Property(c => c.Country).HasMaxLength(2); }); modelBuilder.Entity<Product>(e => { e.Property(p => p.Sku).HasMaxLength(20); e.Property(p => p.Name).HasMaxLength(100); }); modelBuilder.Entity<Address>(e => { e.Property(a => a.Street).HasMaxLength(100); e.Property(a => a.City).HasMaxLength(60); e.Property(a => a.PostalCode).HasMaxLength(12); e.Property(a => a.Country).HasMaxLength(2); }); modelBuilder.Entity<Carrier>(e => e.Property(c => c.Name).HasMaxLength(40)); modelBuilder.Entity<Order>(e => { e.Property(o => o.Status).HasMaxLength(20); e.HasOne(o => o.Customer).WithMany().HasForeignKey(o => o.CustomerId); e.HasOne(o => o.ShippingAddress).WithMany().HasForeignKey(o => o.ShippingAddressId); e.HasOne(o => o.BillingAddress).WithMany().HasForeignKey(o => o.BillingAddressId); e.HasOne(o => o.Carrier).WithMany().HasForeignKey(o => o.CarrierId); e.HasMany(o => o.Lines).WithOne().HasForeignKey(l => l.OrderId); e.HasMany(o => o.Payments).WithOne().HasForeignKey(p => p.OrderId); e.HasIndex(o => new { o.CustomerId, o.OrderedAt }); e.HasIndex(o => o.OrderedAt); }); modelBuilder.Entity<OrderLine>(e => { e.HasOne(l => l.Product).WithMany().HasForeignKey(l => l.ProductId); }); modelBuilder.Entity<Payment>(e => { e.Property(p => p.Method).HasMaxLength(20); }); } } - Download
src/Seeder.cs
20,000 orders from a fixed random seed
Preview contents of src/Seeder.cs
using System.Diagnostics; using Microsoft.EntityFrameworkCore; namespace Api; public static class Seeder { public const int Customers = 1_000; public const int Products = 500; public const int Orders = 20_000; public const int LinesPerOrder = 5; public const int Days = 30; public static readonly DateTime FirstDay = new(2026, 9, 27, 0, 0, 0, DateTimeKind.Utc); public static async Task RunAsync(string connectionString) { var options = new DbContextOptionsBuilder<AppDbContext>().UseNpgsql(connectionString).Options; await using var db = new AppDbContext(options); await db.Database.EnsureDeletedAsync(); await db.Database.EnsureCreatedAsync(); db.ChangeTracker.AutoDetectChangesEnabled = false; var stopwatch = Stopwatch.StartNew(); var random = new Random(220); string[] countries = ["RS", "DE", "US", "GB", "NL", "FR", "PL", "ES"]; string[] statuses = ["Pending", "Paid", "Shipped", "Delivered"]; string[] methods = ["Card", "PayPal", "Bank"]; var customers = Enumerable.Range(1, Customers) .Select(i => new Customer { Name = $"Customer {i}", Email = $"customer{i}@example.com", Country = countries[random.Next(countries.Length)] }) .ToList(); var products = Enumerable.Range(1, Products) .Select(i => new Product { Sku = $"SKU-{i:D5}", Name = $"Product {i}", Price = Math.Round((decimal)(random.NextDouble() * 190 + 10), 2) }) .ToList(); var carriers = new[] { "DHL", "UPS", "FedEx", "GLS", "DPD", "PostNL", "USPS", "Royal Mail" } .Select(name => new Carrier { Name = name }) .ToList(); db.AddRange(carriers); db.AddRange(customers); db.AddRange(products); await db.SaveChangesAsync(); db.ChangeTracker.Clear(); // The extra references draw from their own generator, so the orders, lines and payments // are the same rows as before the references were added. var extra = new Random(221); string[] cities = ["Belgrade", "Berlin", "Austin", "London", "Utrecht", "Lyon", "Gdansk", "Valencia"]; Address NewAddress() => new() { Street = $"{extra.Next(1, 400)} Street {extra.Next(1, 900)}", City = cities[extra.Next(cities.Length)], PostalCode = extra.Next(10_000, 99_999).ToString(), Country = countries[extra.Next(countries.Length)] }; const int chunk = 2_000; for (var start = 0; start < Orders; start += chunk) { var orders = new List<Order>(chunk); for (var i = 0; i < chunk; i++) { var customer = customers[random.Next(customers.Count)]; var orderedAt = FirstDay.AddSeconds(random.NextInt64(Days * 86_400L)); var order = new Order { CustomerId = customer.Id, OrderedAt = orderedAt, Status = statuses[random.Next(statuses.Length)] }; // Every order ships somewhere, half are billed to a second address, // and a pending order has no carrier yet. order.ShippingAddress = NewAddress(); order.BillingAddress = extra.Next(2) == 0 ? NewAddress() : null; order.CarrierId = order.Status == "Pending" ? null : carriers[extra.Next(carriers.Count)].Id; for (var l = 0; l < LinesPerOrder; l++) { var product = products[random.Next(products.Count)]; order.Lines.Add(new OrderLine { ProductId = product.Id, Quantity = random.Next(1, 5), UnitPrice = product.Price }); } var total = order.Lines.Sum(x => x.Quantity * x.UnitPrice); var paymentCount = random.Next(1, 3); for (var p = 0; p < paymentCount; p++) { order.Payments.Add(new Payment { Amount = Math.Round(total / paymentCount, 2), PaidAt = orderedAt.AddMinutes(random.Next(1, 600)), Method = methods[random.Next(methods.Length)] }); } orders.Add(order); } db.AddRange(orders); await db.SaveChangesAsync(); db.ChangeTracker.Clear(); } await db.Database.ExecuteSqlRawAsync("VACUUM ANALYZE"); Console.WriteLine( $"Seeded {Customers} customers, {Products} products, {Orders} orders, " + $"{Orders * LinesPerOrder} lines in {stopwatch.Elapsed.TotalSeconds:F1} s."); } } - Download
src/SqlDump.cs
Runs every query once and writes the SQL EF sent
Preview contents of src/SqlDump.cs
using System.Reflection; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.Logging; namespace Api; // Runs each endpoint's query once and writes the SQL EF Core sent to the server. public static class SqlDump { public static async Task RunAsync(string connectionString, string path) { await using var writer = new StreamWriter(path, append: false); var options = new DbContextOptionsBuilder<AppDbContext>() .UseNpgsql(connectionString) .LogTo(line => writer.WriteLine(line), [RelationalEventId.CommandExecuted], LogLevel.Information) .Options; await using var db = new AppDbContext(options); var efVersion = typeof(DbContext).Assembly .GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion; writer.WriteLine($"-- Microsoft.EntityFrameworkCore {efVersion}"); writer.WriteLine($"-- runtime {Environment.Version}"); writer.WriteLine(); writer.WriteLine("-- GET /orders?customerId=42&page=1"); await OrderQueries.PageAsync(db, 42, 1, CancellationToken.None); writer.WriteLine(); writer.WriteLine("-- GET /orders/report?from=2026-10-01&days=3"); var from = new DateTime(2026, 10, 1, 0, 0, 0, DateTimeKind.Utc); await OrderQueries.RangeAsync(db, from, from.AddDays(3), CancellationToken.None); writer.WriteLine(); writer.WriteLine("-- GET /orders?customerId=42&page=1, with four references"); await OrderQueries.PageWithReferencesAsync(db, 42, 1, CancellationToken.None); writer.WriteLine(); writer.WriteLine("-- GET /orders/report?from=2026-10-01&days=3, with four references"); await OrderQueries.RangeWithReferencesAsync(db, from, from.AddDays(3), CancellationToken.None); writer.WriteLine(); // The same two required references in both orders, to show which join EF Core 11 keeps. var firstOrders = db.Orders.Where(o => o.Id <= 20); writer.WriteLine("-- Two required references, Customer included first"); await firstOrders .Include(o => o.Customer) .Include(o => o.ShippingAddress) .Include(o => o.Payments) .AsSplitQuery() .AsNoTracking() .ToListAsync(); writer.WriteLine(); writer.WriteLine("-- Two required references, ShippingAddress included first"); await firstOrders .Include(o => o.ShippingAddress) .Include(o => o.Customer) .Include(o => o.Payments) .AsSplitQuery() .AsNoTracking() .ToListAsync(); } } - Download
Benchmarks/Benchmarks.csproj
One project, EF 10 on net10.0 and EF 11 on net11.0
Preview contents of Benchmarks/Benchmarks.csproj
<Project Sdk="Microsoft.NET.Sdk"> <!-- One project, two builds: .NET 10 with EF Core 10, and .NET 11 with EF Core 11. BenchmarkDotNet builds each target framework as its own job and compares them side by side. --> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFrameworks>net10.0;net11.0</TargetFrameworks> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> <InvariantGlobalization>true</InvariantGlobalization> <RootNamespace>Api</RootNamespace> <Optimize>true</Optimize> <!-- NU1608: the Npgsql release candidate asks for the EF Core release candidate. See the net11.0 references below. --> <NoWarn>$(NoWarn);NU1608</NoWarn> </PropertyGroup> <ItemGroup> <Compile Include="../src/**/*.cs" Exclude="../src/Program.cs" /> </ItemGroup> <ItemGroup> <PackageReference Include="BenchmarkDotNet" Version="0.16.0-preview.2" /> </ItemGroup> <ItemGroup Condition="'$(TargetFramework)' == 'net10.0'"> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.3" /> </ItemGroup> <ItemGroup Condition="'$(TargetFramework)' == 'net11.0'"> <!-- EF Core 11.0.0 is released. The Npgsql provider for it is not yet, and its release candidate pins the EF Core release candidate, so reference the released EF Core packages directly. --> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="11.0.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="11.0.0" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="11.0.0-rc.3" /> </ItemGroup> </Project> - Download
Benchmarks/Program.cs
The two BenchmarkDotNet jobs, plus the seed and dump-sql commands
Preview contents of Benchmarks/Program.cs
using Api; using BenchmarkDotNet.Configs; using BenchmarkDotNet.Environments; using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Running; var connectionString = Environment.GetEnvironmentVariable("MNW220_DB") ?? "Host=127.0.0.1;Port=5438;Database=orders;Username=mnw220;Password=mnw220"; // seed and dump-sql also run from here, so this one project is enough to reproduce the issue. if (args is ["seed"]) { await Seeder.RunAsync(connectionString); return; } if (args is ["dump-sql", var path]) { await SqlDump.RunAsync(connectionString, path); return; } // EF Core 10 on .NET 10 is the baseline, EF Core 11 on .NET 11 the comparison. var config = DefaultConfig.Instance .AddJob(Job.Default.WithRuntime(CoreRuntime.Core10_0).WithId("EF 10").AsBaseline()) .AddJob(Job.Default.WithRuntime(CoreRuntime.Core11_0).WithId("EF 11")); BenchmarkRunner.Run<Api.SplitQueryBenchmarks>(config, args); - Download
Benchmarks/SplitQueryBenchmarks.cs
The four benchmarks
Preview contents of Benchmarks/SplitQueryBenchmarks.cs
using BenchmarkDotNet.Attributes; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; namespace Api; [MemoryDiagnoser] public class SplitQueryBenchmarks { private const string ConnectionString = "Host=127.0.0.1;Port=5438;Database=orders;Username=mnw220;Password=mnw220"; private static readonly DateTime From = Seeder.FirstDay.AddDays(10); private static readonly DateTime To = From.AddDays(3); private PooledDbContextFactory<AppDbContext> _factory = null!; [GlobalSetup] public void Setup() => _factory = new PooledDbContextFactory<AppDbContext>( new DbContextOptionsBuilder<AppDbContext>().UseNpgsql(ConnectionString).Options); [Benchmark] public async Task<int> Page() { await using var db = _factory.CreateDbContext(); var orders = await OrderQueries.PageAsync(db, customerId: 42, page: 0, CancellationToken.None); return orders.Count; } [Benchmark] public async Task<int> Report() { await using var db = _factory.CreateDbContext(); var orders = await OrderQueries.RangeAsync(db, From, To, CancellationToken.None); return orders.Count; } [Benchmark] public async Task<int> PageFourReferences() { await using var db = _factory.CreateDbContext(); var orders = await OrderQueries.PageWithReferencesAsync(db, customerId: 42, page: 0, CancellationToken.None); return orders.Count; } [Benchmark] public async Task<int> ReportFourReferences() { await using var db = _factory.CreateDbContext(); var orders = await OrderQueries.RangeWithReferencesAsync(db, From, To, CancellationToken.None); return orders.Count; } } - Download
Benchmarks/global.json
SDK selection for the benchmark project
Preview contents of Benchmarks/global.json
{ "sdk": { "version": "11.0.100", "rollForward": "latestFeature" } } - Download
output/sql-ef10.txt
The SQL EF 10 sent for every query
Preview contents of output/sql-ef10.txt
-- Microsoft.EntityFrameworkCore 10.0.4 -- runtime 10.0.9 -- GET /orders?customerId=42&page=1 info: 11/10/2026 18:52:19.209 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (25ms) [Parameters=[@customerId='?' (DbType = Int32), @p='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30'] SELECT o0."Id", o0."BillingAddressId", o0."CarrierId", o0."CustomerId", o0."OrderedAt", o0."ShippingAddressId", o0."Status", c."Id", c."Country", c."Email", c."Name" FROM ( SELECT o."Id", o."BillingAddressId", o."CarrierId", o."CustomerId", o."OrderedAt", o."ShippingAddressId", o."Status" FROM "Orders" AS o WHERE o."CustomerId" = @customerId ORDER BY o."OrderedAt" DESC, o."Id" LIMIT @p OFFSET @p ) AS o0 INNER JOIN "Customers" AS c ON o0."CustomerId" = c."Id" ORDER BY o0."OrderedAt" DESC, o0."Id", c."Id" info: 11/10/2026 18:52:19.230 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (2ms) [Parameters=[@customerId='?' (DbType = Int32), @p='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30'] SELECT s."Id", s."OrderId", s."ProductId", s."Quantity", s."UnitPrice", s."Id0", s."Name", s."Price", s."Sku", o0."Id", c."Id" FROM ( SELECT o."Id", o."CustomerId", o."OrderedAt" FROM "Orders" AS o WHERE o."CustomerId" = @customerId ORDER BY o."OrderedAt" DESC, o."Id" LIMIT @p OFFSET @p ) AS o0 INNER JOIN "Customers" AS c ON o0."CustomerId" = c."Id" INNER JOIN ( SELECT o1."Id", o1."OrderId", o1."ProductId", o1."Quantity", o1."UnitPrice", p."Id" AS "Id0", p."Name", p."Price", p."Sku" FROM "OrderLines" AS o1 INNER JOIN "Products" AS p ON o1."ProductId" = p."Id" ) AS s ON o0."Id" = s."OrderId" ORDER BY o0."OrderedAt" DESC, o0."Id", c."Id" info: 11/10/2026 18:52:19.235 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (1ms) [Parameters=[@customerId='?' (DbType = Int32), @p='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30'] SELECT p0."Id", p0."Amount", p0."Method", p0."OrderId", p0."PaidAt", o0."Id", c."Id" FROM ( SELECT o."Id", o."CustomerId", o."OrderedAt" FROM "Orders" AS o WHERE o."CustomerId" = @customerId ORDER BY o."OrderedAt" DESC, o."Id" LIMIT @p OFFSET @p ) AS o0 INNER JOIN "Customers" AS c ON o0."CustomerId" = c."Id" INNER JOIN "Payments" AS p0 ON o0."Id" = p0."OrderId" ORDER BY o0."OrderedAt" DESC, o0."Id", c."Id" -- GET /orders/report?from=2026-10-01&days=3 info: 11/10/2026 18:52:19.260 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (4ms) [Parameters=[@from='?' (DbType = DateTime), @to='?' (DbType = DateTime)], CommandType='Text', CommandTimeout='30'] SELECT o."Id", o."BillingAddressId", o."CarrierId", o."CustomerId", o."OrderedAt", o."ShippingAddressId", o."Status", c."Id", c."Country", c."Email", c."Name" FROM "Orders" AS o INNER JOIN "Customers" AS c ON o."CustomerId" = c."Id" WHERE o."OrderedAt" >= @from AND o."OrderedAt" < @to ORDER BY o."Id", c."Id" info: 11/10/2026 18:52:19.280 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (12ms) [Parameters=[@from='?' (DbType = DateTime), @to='?' (DbType = DateTime)], CommandType='Text', CommandTimeout='30'] SELECT s."Id", s."OrderId", s."ProductId", s."Quantity", s."UnitPrice", s."Id0", s."Name", s."Price", s."Sku", o."Id", c."Id" FROM "Orders" AS o INNER JOIN "Customers" AS c ON o."CustomerId" = c."Id" INNER JOIN ( SELECT o0."Id", o0."OrderId", o0."ProductId", o0."Quantity", o0."UnitPrice", p."Id" AS "Id0", p."Name", p."Price", p."Sku" FROM "OrderLines" AS o0 INNER JOIN "Products" AS p ON o0."ProductId" = p."Id" ) AS s ON o."Id" = s."OrderId" WHERE o."OrderedAt" >= @from AND o."OrderedAt" < @to ORDER BY o."Id", c."Id" info: 11/10/2026 18:52:19.310 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (3ms) [Parameters=[@from='?' (DbType = DateTime), @to='?' (DbType = DateTime)], CommandType='Text', CommandTimeout='30'] SELECT p0."Id", p0."Amount", p0."Method", p0."OrderId", p0."PaidAt", o."Id", c."Id" FROM "Orders" AS o INNER JOIN "Customers" AS c ON o."CustomerId" = c."Id" INNER JOIN "Payments" AS p0 ON o."Id" = p0."OrderId" WHERE o."OrderedAt" >= @from AND o."OrderedAt" < @to ORDER BY o."Id", c."Id" -- GET /orders?customerId=42&page=1, with four references info: 11/10/2026 18:52:19.376 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (1ms) [Parameters=[@customerId='?' (DbType = Int32), @p='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30'] SELECT o0."Id", o0."BillingAddressId", o0."CarrierId", o0."CustomerId", o0."OrderedAt", o0."ShippingAddressId", o0."Status", c."Id", c."Country", c."Email", c."Name", a."Id", a."City", a."Country", a."PostalCode", a."Street", a0."Id", a0."City", a0."Country", a0."PostalCode", a0."Street", c0."Id", c0."Name" FROM ( SELECT o."Id", o."BillingAddressId", o."CarrierId", o."CustomerId", o."OrderedAt", o."ShippingAddressId", o."Status" FROM "Orders" AS o WHERE o."CustomerId" = @customerId ORDER BY o."OrderedAt" DESC, o."Id" LIMIT @p OFFSET @p ) AS o0 INNER JOIN "Customers" AS c ON o0."CustomerId" = c."Id" INNER JOIN "Addresses" AS a ON o0."ShippingAddressId" = a."Id" LEFT JOIN "Addresses" AS a0 ON o0."BillingAddressId" = a0."Id" LEFT JOIN "Carriers" AS c0 ON o0."CarrierId" = c0."Id" ORDER BY o0."OrderedAt" DESC, o0."Id", c."Id", a."Id", a0."Id", c0."Id" info: 11/10/2026 18:52:19.380 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (1ms) [Parameters=[@customerId='?' (DbType = Int32), @p='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30'] SELECT s."Id", s."OrderId", s."ProductId", s."Quantity", s."UnitPrice", s."Id0", s."Name", s."Price", s."Sku", o0."Id", c."Id", a."Id", a0."Id", c0."Id" FROM ( SELECT o."Id", o."BillingAddressId", o."CarrierId", o."CustomerId", o."OrderedAt", o."ShippingAddressId" FROM "Orders" AS o WHERE o."CustomerId" = @customerId ORDER BY o."OrderedAt" DESC, o."Id" LIMIT @p OFFSET @p ) AS o0 INNER JOIN "Customers" AS c ON o0."CustomerId" = c."Id" INNER JOIN "Addresses" AS a ON o0."ShippingAddressId" = a."Id" LEFT JOIN "Addresses" AS a0 ON o0."BillingAddressId" = a0."Id" LEFT JOIN "Carriers" AS c0 ON o0."CarrierId" = c0."Id" INNER JOIN ( SELECT o1."Id", o1."OrderId", o1."ProductId", o1."Quantity", o1."UnitPrice", p."Id" AS "Id0", p."Name", p."Price", p."Sku" FROM "OrderLines" AS o1 INNER JOIN "Products" AS p ON o1."ProductId" = p."Id" ) AS s ON o0."Id" = s."OrderId" ORDER BY o0."OrderedAt" DESC, o0."Id", c."Id", a."Id", a0."Id", c0."Id" info: 11/10/2026 18:52:19.381 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (1ms) [Parameters=[@customerId='?' (DbType = Int32), @p='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30'] SELECT p0."Id", p0."Amount", p0."Method", p0."OrderId", p0."PaidAt", o0."Id", c."Id", a."Id", a0."Id", c0."Id" FROM ( SELECT o."Id", o."BillingAddressId", o."CarrierId", o."CustomerId", o."OrderedAt", o."ShippingAddressId" FROM "Orders" AS o WHERE o."CustomerId" = @customerId ORDER BY o."OrderedAt" DESC, o."Id" LIMIT @p OFFSET @p ) AS o0 INNER JOIN "Customers" AS c ON o0."CustomerId" = c."Id" INNER JOIN "Addresses" AS a ON o0."ShippingAddressId" = a."Id" LEFT JOIN "Addresses" AS a0 ON o0."BillingAddressId" = a0."Id" LEFT JOIN "Carriers" AS c0 ON o0."CarrierId" = c0."Id" INNER JOIN "Payments" AS p0 ON o0."Id" = p0."OrderId" ORDER BY o0."OrderedAt" DESC, o0."Id", c."Id", a."Id", a0."Id", c0."Id" -- GET /orders/report?from=2026-10-01&days=3, with four references info: 11/10/2026 18:52:19.407 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (5ms) [Parameters=[@from='?' (DbType = DateTime), @to='?' (DbType = DateTime)], CommandType='Text', CommandTimeout='30'] SELECT o."Id", o."BillingAddressId", o."CarrierId", o."CustomerId", o."OrderedAt", o."ShippingAddressId", o."Status", c."Id", c."Country", c."Email", c."Name", a."Id", a."City", a."Country", a."PostalCode", a."Street", a0."Id", a0."City", a0."Country", a0."PostalCode", a0."Street", c0."Id", c0."Name" FROM "Orders" AS o INNER JOIN "Customers" AS c ON o."CustomerId" = c."Id" INNER JOIN "Addresses" AS a ON o."ShippingAddressId" = a."Id" LEFT JOIN "Addresses" AS a0 ON o."BillingAddressId" = a0."Id" LEFT JOIN "Carriers" AS c0 ON o."CarrierId" = c0."Id" WHERE o."OrderedAt" >= @from AND o."OrderedAt" < @to ORDER BY o."Id", c."Id", a."Id", a0."Id", c0."Id" info: 11/10/2026 18:52:19.431 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (13ms) [Parameters=[@from='?' (DbType = DateTime), @to='?' (DbType = DateTime)], CommandType='Text', CommandTimeout='30'] SELECT s."Id", s."OrderId", s."ProductId", s."Quantity", s."UnitPrice", s."Id0", s."Name", s."Price", s."Sku", o."Id", c."Id", a."Id", a0."Id", c0."Id" FROM "Orders" AS o INNER JOIN "Customers" AS c ON o."CustomerId" = c."Id" INNER JOIN "Addresses" AS a ON o."ShippingAddressId" = a."Id" LEFT JOIN "Addresses" AS a0 ON o."BillingAddressId" = a0."Id" LEFT JOIN "Carriers" AS c0 ON o."CarrierId" = c0."Id" INNER JOIN ( SELECT o0."Id", o0."OrderId", o0."ProductId", o0."Quantity", o0."UnitPrice", p."Id" AS "Id0", p."Name", p."Price", p."Sku" FROM "OrderLines" AS o0 INNER JOIN "Products" AS p ON o0."ProductId" = p."Id" ) AS s ON o."Id" = s."OrderId" WHERE o."OrderedAt" >= @from AND o."OrderedAt" < @to ORDER BY o."Id", c."Id", a."Id", a0."Id", c0."Id" info: 11/10/2026 18:52:19.474 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (7ms) [Parameters=[@from='?' (DbType = DateTime), @to='?' (DbType = DateTime)], CommandType='Text', CommandTimeout='30'] SELECT p0."Id", p0."Amount", p0."Method", p0."OrderId", p0."PaidAt", o."Id", c."Id", a."Id", a0."Id", c0."Id" FROM "Orders" AS o INNER JOIN "Customers" AS c ON o."CustomerId" = c."Id" INNER JOIN "Addresses" AS a ON o."ShippingAddressId" = a."Id" LEFT JOIN "Addresses" AS a0 ON o."BillingAddressId" = a0."Id" LEFT JOIN "Carriers" AS c0 ON o."CarrierId" = c0."Id" INNER JOIN "Payments" AS p0 ON o."Id" = p0."OrderId" WHERE o."OrderedAt" >= @from AND o."OrderedAt" < @to ORDER BY o."Id", c."Id", a."Id", a0."Id", c0."Id" -- Two required references, Customer included first info: 11/10/2026 18:52:19.531 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] SELECT o."Id", o."BillingAddressId", o."CarrierId", o."CustomerId", o."OrderedAt", o."ShippingAddressId", o."Status", c."Id", c."Country", c."Email", c."Name", a."Id", a."City", a."Country", a."PostalCode", a."Street" FROM "Orders" AS o INNER JOIN "Customers" AS c ON o."CustomerId" = c."Id" INNER JOIN "Addresses" AS a ON o."ShippingAddressId" = a."Id" WHERE o."Id" <= 20 ORDER BY o."Id", c."Id", a."Id" info: 11/10/2026 18:52:19.534 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] SELECT p."Id", p."Amount", p."Method", p."OrderId", p."PaidAt", o."Id", c."Id", a."Id" FROM "Orders" AS o INNER JOIN "Customers" AS c ON o."CustomerId" = c."Id" INNER JOIN "Addresses" AS a ON o."ShippingAddressId" = a."Id" INNER JOIN "Payments" AS p ON o."Id" = p."OrderId" WHERE o."Id" <= 20 ORDER BY o."Id", c."Id", a."Id" -- Two required references, ShippingAddress included first info: 11/10/2026 18:52:19.547 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] SELECT o."Id", o."BillingAddressId", o."CarrierId", o."CustomerId", o."OrderedAt", o."ShippingAddressId", o."Status", a."Id", a."City", a."Country", a."PostalCode", a."Street", c."Id", c."Country", c."Email", c."Name" FROM "Orders" AS o INNER JOIN "Addresses" AS a ON o."ShippingAddressId" = a."Id" INNER JOIN "Customers" AS c ON o."CustomerId" = c."Id" WHERE o."Id" <= 20 ORDER BY o."Id", a."Id", c."Id" info: 11/10/2026 18:52:19.550 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] SELECT p."Id", p."Amount", p."Method", p."OrderId", p."PaidAt", o."Id", a."Id", c."Id" FROM "Orders" AS o INNER JOIN "Addresses" AS a ON o."ShippingAddressId" = a."Id" INNER JOIN "Customers" AS c ON o."CustomerId" = c."Id" INNER JOIN "Payments" AS p ON o."Id" = p."OrderId" WHERE o."Id" <= 20 ORDER BY o."Id", a."Id", c."Id" - Download
output/sql-ef11.txt
The SQL EF 11 sent for the same queries
Preview contents of output/sql-ef11.txt
-- Microsoft.EntityFrameworkCore 11.0.0 -- runtime 11.0.0 -- GET /orders?customerId=42&page=1 info: 11/10/2026 18:52:20.663 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (24ms) [Parameters=[@customerId='?' (DbType = Int32), @p='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30'] SELECT o0."Id", o0."BillingAddressId", o0."CarrierId", o0."CustomerId", o0."OrderedAt", o0."ShippingAddressId", o0."Status", c."Id", c."Country", c."Email", c."Name" FROM ( SELECT o."Id", o."BillingAddressId", o."CarrierId", o."CustomerId", o."OrderedAt", o."ShippingAddressId", o."Status" FROM "Orders" AS o WHERE o."CustomerId" = @customerId ORDER BY o."OrderedAt" DESC, o."Id" LIMIT @p OFFSET @p ) AS o0 INNER JOIN "Customers" AS c ON o0."CustomerId" = c."Id" ORDER BY o0."OrderedAt" DESC, o0."Id" info: 11/10/2026 18:52:20.685 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (2ms) [Parameters=[@customerId='?' (DbType = Int32), @p='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30'] SELECT s."Id", s."OrderId", s."ProductId", s."Quantity", s."UnitPrice", s."Id0", s."Name", s."Price", s."Sku", o0."Id" FROM ( SELECT o."Id", o."OrderedAt" FROM "Orders" AS o WHERE o."CustomerId" = @customerId ORDER BY o."OrderedAt" DESC, o."Id" LIMIT @p OFFSET @p ) AS o0 INNER JOIN ( SELECT o1."Id", o1."OrderId", o1."ProductId", o1."Quantity", o1."UnitPrice", p."Id" AS "Id0", p."Name", p."Price", p."Sku" FROM "OrderLines" AS o1 INNER JOIN "Products" AS p ON o1."ProductId" = p."Id" ) AS s ON o0."Id" = s."OrderId" ORDER BY o0."OrderedAt" DESC, o0."Id" info: 11/10/2026 18:52:20.689 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (1ms) [Parameters=[@customerId='?' (DbType = Int32), @p='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30'] SELECT p0."Id", p0."Amount", p0."Method", p0."OrderId", p0."PaidAt", o0."Id" FROM ( SELECT o."Id", o."OrderedAt" FROM "Orders" AS o WHERE o."CustomerId" = @customerId ORDER BY o."OrderedAt" DESC, o."Id" LIMIT @p OFFSET @p ) AS o0 INNER JOIN "Payments" AS p0 ON o0."Id" = p0."OrderId" ORDER BY o0."OrderedAt" DESC, o0."Id" -- GET /orders/report?from=2026-10-01&days=3 info: 11/10/2026 18:52:20.713 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (3ms) [Parameters=[@from='?' (DbType = DateTime), @to='?' (DbType = DateTime)], CommandType='Text', CommandTimeout='30'] SELECT o."Id", o."BillingAddressId", o."CarrierId", o."CustomerId", o."OrderedAt", o."ShippingAddressId", o."Status", c."Id", c."Country", c."Email", c."Name" FROM "Orders" AS o INNER JOIN "Customers" AS c ON o."CustomerId" = c."Id" WHERE o."OrderedAt" >= @from AND o."OrderedAt" < @to ORDER BY o."Id" info: 11/10/2026 18:52:20.727 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (7ms) [Parameters=[@from='?' (DbType = DateTime), @to='?' (DbType = DateTime)], CommandType='Text', CommandTimeout='30'] SELECT s."Id", s."OrderId", s."ProductId", s."Quantity", s."UnitPrice", s."Id0", s."Name", s."Price", s."Sku", o."Id" FROM "Orders" AS o INNER JOIN ( SELECT o0."Id", o0."OrderId", o0."ProductId", o0."Quantity", o0."UnitPrice", p."Id" AS "Id0", p."Name", p."Price", p."Sku" FROM "OrderLines" AS o0 INNER JOIN "Products" AS p ON o0."ProductId" = p."Id" ) AS s ON o."Id" = s."OrderId" WHERE o."OrderedAt" >= @from AND o."OrderedAt" < @to ORDER BY o."Id" info: 11/10/2026 18:52:20.755 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (3ms) [Parameters=[@from='?' (DbType = DateTime), @to='?' (DbType = DateTime)], CommandType='Text', CommandTimeout='30'] SELECT p0."Id", p0."Amount", p0."Method", p0."OrderId", p0."PaidAt", o."Id" FROM "Orders" AS o INNER JOIN "Payments" AS p0 ON o."Id" = p0."OrderId" WHERE o."OrderedAt" >= @from AND o."OrderedAt" < @to ORDER BY o."Id" -- GET /orders?customerId=42&page=1, with four references info: 11/10/2026 18:52:20.820 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (2ms) [Parameters=[@customerId='?' (DbType = Int32), @p='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30'] SELECT o0."Id", o0."BillingAddressId", o0."CarrierId", o0."CustomerId", o0."OrderedAt", o0."ShippingAddressId", o0."Status", c."Id", c."Country", c."Email", c."Name", a."Id", a."City", a."Country", a."PostalCode", a."Street", a0."Id", a0."City", a0."Country", a0."PostalCode", a0."Street", c0."Id", c0."Name" FROM ( SELECT o."Id", o."BillingAddressId", o."CarrierId", o."CustomerId", o."OrderedAt", o."ShippingAddressId", o."Status" FROM "Orders" AS o WHERE o."CustomerId" = @customerId ORDER BY o."OrderedAt" DESC, o."Id" LIMIT @p OFFSET @p ) AS o0 INNER JOIN "Customers" AS c ON o0."CustomerId" = c."Id" INNER JOIN "Addresses" AS a ON o0."ShippingAddressId" = a."Id" LEFT JOIN "Addresses" AS a0 ON o0."BillingAddressId" = a0."Id" LEFT JOIN "Carriers" AS c0 ON o0."CarrierId" = c0."Id" ORDER BY o0."OrderedAt" DESC, o0."Id" info: 11/10/2026 18:52:20.824 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (1ms) [Parameters=[@customerId='?' (DbType = Int32), @p='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30'] SELECT s."Id", s."OrderId", s."ProductId", s."Quantity", s."UnitPrice", s."Id0", s."Name", s."Price", s."Sku", o0."Id" FROM ( SELECT o."Id", o."OrderedAt", o."ShippingAddressId" FROM "Orders" AS o WHERE o."CustomerId" = @customerId ORDER BY o."OrderedAt" DESC, o."Id" LIMIT @p OFFSET @p ) AS o0 INNER JOIN "Addresses" AS a ON o0."ShippingAddressId" = a."Id" INNER JOIN ( SELECT o1."Id", o1."OrderId", o1."ProductId", o1."Quantity", o1."UnitPrice", p."Id" AS "Id0", p."Name", p."Price", p."Sku" FROM "OrderLines" AS o1 INNER JOIN "Products" AS p ON o1."ProductId" = p."Id" ) AS s ON o0."Id" = s."OrderId" ORDER BY o0."OrderedAt" DESC, o0."Id" info: 11/10/2026 18:52:20.825 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (1ms) [Parameters=[@customerId='?' (DbType = Int32), @p='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30'] SELECT p0."Id", p0."Amount", p0."Method", p0."OrderId", p0."PaidAt", o0."Id" FROM ( SELECT o."Id", o."OrderedAt", o."ShippingAddressId" FROM "Orders" AS o WHERE o."CustomerId" = @customerId ORDER BY o."OrderedAt" DESC, o."Id" LIMIT @p OFFSET @p ) AS o0 INNER JOIN "Addresses" AS a ON o0."ShippingAddressId" = a."Id" INNER JOIN "Payments" AS p0 ON o0."Id" = p0."OrderId" ORDER BY o0."OrderedAt" DESC, o0."Id" -- GET /orders/report?from=2026-10-01&days=3, with four references info: 11/10/2026 18:52:20.849 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (6ms) [Parameters=[@from='?' (DbType = DateTime), @to='?' (DbType = DateTime)], CommandType='Text', CommandTimeout='30'] SELECT o."Id", o."BillingAddressId", o."CarrierId", o."CustomerId", o."OrderedAt", o."ShippingAddressId", o."Status", c."Id", c."Country", c."Email", c."Name", a."Id", a."City", a."Country", a."PostalCode", a."Street", a0."Id", a0."City", a0."Country", a0."PostalCode", a0."Street", c0."Id", c0."Name" FROM "Orders" AS o INNER JOIN "Customers" AS c ON o."CustomerId" = c."Id" INNER JOIN "Addresses" AS a ON o."ShippingAddressId" = a."Id" LEFT JOIN "Addresses" AS a0 ON o."BillingAddressId" = a0."Id" LEFT JOIN "Carriers" AS c0 ON o."CarrierId" = c0."Id" WHERE o."OrderedAt" >= @from AND o."OrderedAt" < @to ORDER BY o."Id" info: 11/10/2026 18:52:20.868 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (7ms) [Parameters=[@from='?' (DbType = DateTime), @to='?' (DbType = DateTime)], CommandType='Text', CommandTimeout='30'] SELECT s."Id", s."OrderId", s."ProductId", s."Quantity", s."UnitPrice", s."Id0", s."Name", s."Price", s."Sku", o."Id" FROM "Orders" AS o INNER JOIN "Addresses" AS a ON o."ShippingAddressId" = a."Id" INNER JOIN ( SELECT o0."Id", o0."OrderId", o0."ProductId", o0."Quantity", o0."UnitPrice", p."Id" AS "Id0", p."Name", p."Price", p."Sku" FROM "OrderLines" AS o0 INNER JOIN "Products" AS p ON o0."ProductId" = p."Id" ) AS s ON o."Id" = s."OrderId" WHERE o."OrderedAt" >= @from AND o."OrderedAt" < @to ORDER BY o."Id" info: 11/10/2026 18:52:20.902 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (4ms) [Parameters=[@from='?' (DbType = DateTime), @to='?' (DbType = DateTime)], CommandType='Text', CommandTimeout='30'] SELECT p0."Id", p0."Amount", p0."Method", p0."OrderId", p0."PaidAt", o."Id" FROM "Orders" AS o INNER JOIN "Addresses" AS a ON o."ShippingAddressId" = a."Id" INNER JOIN "Payments" AS p0 ON o."Id" = p0."OrderId" WHERE o."OrderedAt" >= @from AND o."OrderedAt" < @to ORDER BY o."Id" -- Two required references, Customer included first info: 11/10/2026 18:52:20.956 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] SELECT o."Id", o."BillingAddressId", o."CarrierId", o."CustomerId", o."OrderedAt", o."ShippingAddressId", o."Status", c."Id", c."Country", c."Email", c."Name", a."Id", a."City", a."Country", a."PostalCode", a."Street" FROM "Orders" AS o INNER JOIN "Customers" AS c ON o."CustomerId" = c."Id" INNER JOIN "Addresses" AS a ON o."ShippingAddressId" = a."Id" WHERE o."Id" <= 20 ORDER BY o."Id" info: 11/10/2026 18:52:20.959 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] SELECT p."Id", p."Amount", p."Method", p."OrderId", p."PaidAt", o."Id" FROM "Orders" AS o INNER JOIN "Addresses" AS a ON o."ShippingAddressId" = a."Id" INNER JOIN "Payments" AS p ON o."Id" = p."OrderId" WHERE o."Id" <= 20 ORDER BY o."Id" -- Two required references, ShippingAddress included first info: 11/10/2026 18:52:20.971 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] SELECT o."Id", o."BillingAddressId", o."CarrierId", o."CustomerId", o."OrderedAt", o."ShippingAddressId", o."Status", a."Id", a."City", a."Country", a."PostalCode", a."Street", c."Id", c."Country", c."Email", c."Name" FROM "Orders" AS o INNER JOIN "Addresses" AS a ON o."ShippingAddressId" = a."Id" INNER JOIN "Customers" AS c ON o."CustomerId" = c."Id" WHERE o."Id" <= 20 ORDER BY o."Id" info: 11/10/2026 18:52:20.973 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command) Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30'] SELECT p."Id", p."Amount", p."Method", p."OrderId", p."PaidAt", o."Id" FROM "Orders" AS o INNER JOIN "Customers" AS c ON o."CustomerId" = c."Id" INNER JOIN "Payments" AS p ON o."Id" = p."OrderId" WHERE o."Id" <= 20 ORDER BY o."Id" - Download
output/benchmarkdotnet.md
BenchmarkDotNet results from my machine
Preview contents of output/benchmarkdotnet.md
``` BenchmarkDotNet v0.16.0-preview.2, Windows 11 (10.0.26200.9711/25H2/2025Update/HudsonValley2) AMD Ryzen 9 9950X3D 4.30GHz, 1 CPU, 32 logical and 16 physical cores Memory: 61.67 GB Total, 22.63 GB Available .NET SDK 11.0.100 [Host] : .NET 11.0.0 (11.0.0, 11.0.26.53018), X64 RyuJIT x86-64-v4 EF 10 : .NET 10.0.9 (10.0.9, 10.0.926.27113), X64 RyuJIT x86-64-v4 EF 11 : .NET 11.0.0 (11.0.0, 11.0.26.53018), X64 RyuJIT x86-64-v4 ``` | Method | Job | Runtime | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio | |--------------------- |------ |---------- |----------:|----------:|----------:|------:|--------:|---------:|---------:|---------:|------------:|------------:| | Page | EF 10 | .NET 10.0 | 2.473 ms | 0.0422 ms | 0.0374 ms | 1.00 | 0.00 | 3.9063 | - | - | 277.04 KB | 1.00 | | Page | EF 11 | .NET 11.0 | 2.213 ms | 0.0431 ms | 0.0708 ms | 0.89 | 0.03 | 3.9063 | - | - | 262.19 KB | 0.95 | | | | | | | | | | | | | | | | Report | EF 10 | .NET 10.0 | 47.677 ms | 0.9435 ms | 1.6771 ms | 1.00 | 0.00 | 818.1818 | 818.1818 | 454.5455 | 22874.99 KB | 1.00 | | Report | EF 11 | .NET 11.0 | 44.379 ms | 0.8675 ms | 1.6506 ms | 0.93 | 0.05 | 750.0000 | 750.0000 | 500.0000 | 21560.37 KB | 0.94 | | | | | | | | | | | | | | | | PageFourReferences | EF 10 | .NET 10.0 | 3.317 ms | 0.0657 ms | 0.0757 ms | 1.00 | 0.00 | 3.9063 | - | - | 347.33 KB | 1.00 | | PageFourReferences | EF 11 | .NET 11.0 | 2.508 ms | 0.0409 ms | 0.0382 ms | 0.76 | 0.02 | 3.9063 | - | - | 310.16 KB | 0.89 | | | | | | | | | | | | | | | | ReportFourReferences | EF 10 | .NET 10.0 | 67.450 ms | 1.1850 ms | 1.0505 ms | 1.00 | 0.00 | 857.1429 | 857.1429 | 714.2857 | 28708.99 KB | 1.00 | | ReportFourReferences | EF 11 | .NET 11.0 | 53.215 ms | 1.0123 ms | 1.0395 ms | 0.79 | 0.02 | 888.8889 | 888.8889 | 555.5556 | 25136.02 KB | 0.88 |
2. Run it
Open a terminal in that folder and run:
docker compose up -d --wait
cd Benchmarks
dotnet run -c Release -f net11.0 -- seed
dotnet run -c Release -f net10.0 -- dump-sql sql-ef10.txt
dotnet run -c Release -f net11.0 -- dump-sql sql-ef11.txt
dotnet run -c Release -f net11.0 -- --filter '*'seed recreates the schema and inserts 20,000 orders with 5 lines and 1 to 2 payments each, from a fixed random seed. dump-sql runs every query once and writes the SQL that EF sent, and the target framework picks the EF version. Compare your two files with output/sql-ef10.txt and output/sql-ef11.txt. The statements should match, the timestamps will not.
The last command runs the four benchmarks on both runtimes and takes several minutes. Compare the result with output/benchmarkdotnet.md. Your timings will differ, but the ratios between EF 10 and EF 11 should be close.
3. What to look for
- One reference. In sql-ef10.txt the lines and payments statements of the first two queries join Customers and sort by its key. In sql-ef11.txt neither statement does. The first statement of each query keeps the join, because it returns the customer.
- Four references. In the two four-reference queries, EF 10 repeats all four reference joins in both collection statements. EF 11 removes three of them and keeps the join for ShippingAddress.
- Include order. The last two queries include the same two required references in both orders. On EF 11, the join that stays is the one for the reference included second.
- The benchmarks. Page and Report include one reference, and the FourReferences versions include four. In output/benchmarkdotnet.md the EF 11 ratios are 0.89, 0.93, 0.76, and 0.79.
4. Change something
Make one change at a time in src/Queries.cs, dump the SQL again on both target frameworks, and restore the original code before trying the next one:
- In
RangeWithReferencesAsync, move.Include(o => o.ShippingAddress)above.Include(o => o.Customer). On EF 11 the collection statements now keep theCustomersjoin and drop the one for the shipping address. - In
RangeAsync, remove.AsSplitQuery(). EF sends one statement with every join. On EF 11 it still joinsCustomers, because that's where the customer's columns come from, butc."Id"and the product's key are gone from theORDER BY.
When you are done, docker compose down -v removes the container and its data.