Skip to content

Pillar guide

.NET Performance Guide

A running series of BenchmarkDotNet studies on .NET 10 — API frameworks, EF Core query patterns, and more. Real workloads, published numbers, runnable source.

Last updated Sep 21, 2026

dotnet-performance benchmarks

Most .NET performance advice is either a microbenchmark measuring nothing your application does, or a war story with no numbers attached. This guide is an attempt at the third thing: a running series of benchmarks that put realistic workloads under BenchmarkDotNet, publish the full results, and ship the source so you can disagree with us on your own hardware.

Every study here runs on .NET 10 against PostgreSQL 16 through Entity Framework Core 10, with databases provisioned by Testcontainers so the environment is reproducible rather than described. Where a result is inconvenient for a tool we use in production, it is published anyway.

Why these benchmarks look different from the ones you have seen

The official benchmark for almost any web framework measures the framework in isolation. A load generator fires requests at an endpoint that returns a constant, and the result is a very large requests-per-second number. That measurement is not wrong, it is just answering a question you do not have. No one is paid to operate an API that returns a constant.

Your endpoints do work. They deserialize a request body, run validation, open a database connection, execute one or more queries, materialize entities, project them into response DTOs, serialize those to JSON, and push the result back through the middleware pipeline. The framework’s share of that total is the number that decides whether switching frameworks is worth a quarter of engineering time.

Measured that way, framework overhead shrinks dramatically in relative terms — and other decisions grow. That shift is the most useful thing in this guide, and it only becomes visible when the benchmark includes the database.

The methodology, stated plainly

Each study holds everything constant except the one variable under test. In the API framework comparison, three projects expose identical CRUD endpoints over the same ProductService, the same EF Core DbContext, and the same PostgreSQL instance; only the framework layer in front differs. In the EF Core study, the query shape changes while the schema, seed data, and result set stay fixed.

Results are reported with mean, standard deviation, and allocated bytes, because a mean alone hides the variance that actually wakes people up at night. Where the spread is wide enough to matter, the post says so rather than rounding it away.

Published: API framework overhead on .NET 10

FastEndpoints vs Minimal API vs Controllers benchmarks all three ASP.NET Core approaches doing real CRUD against PostgreSQL, with FluentValidation and realistic JSON payloads.

The headline, on a single-entity read:

FrameworkMean (μs)Allocated (KB)
MVC Controllers4,259278.71
Minimal API3,69897.79
FastEndpoints3,54698.46

The latency gap between the full MVC pipeline and the Minimal-API-derived frameworks lands in the 15–20% range across read scenarios. The allocation gap is far more dramatic: Controllers allocate roughly 2.8x more memory per request, a difference that shows up as GC pressure and, eventually, as instance count on your hosting bill.

The honest caveat, which the post makes and this page repeats: on write-heavy scenarios the picture flattens. UpdateEntity put FastEndpoints marginally behind Controllers on mean latency while still allocating less. Anyone selling you a single multiplier for framework choice is rounding away the scenarios that disagree with them.

What this justifies, and what it does not

A 15–20% latency reduction and a 2.8x allocation reduction is a real result worth having on a greenfield project, where the choice is free. It is a much harder case for a rewrite of a working Controllers codebase, where the cost is measured in months and regression risk. The numbers support “choose this next time” considerably better than they support “migrate immediately.”

Published: the N+1 query problem in EF Core

Benchmarking every fix for the N+1 problem on PostgreSQL takes the most common data-access mistake in .NET and measures every standard remedy against it.

ApproachMeanSQL QueriesAllocatedRatio vs N+1
Lazy loading (N+1)336.9 ms~50010,843 KB1.00×
Split query7.0 ms31,232 KB0.021×
Eager loading (Include)5.4 ms1889 KB0.016×
Projection (Select)2.9 ms1344 KB0.0085×
Compiled projection2.4 ms1327 KB0.0072×
Raw SQL (manual JOIN)2.4 ms1312 KB0.0072×

Read the first and last rows together. The naive lazy-loading pattern takes 336.9 ms and roughly 500 round trips. A compiled projection takes 2.4 ms and one — about 140x faster, on identical data, returning identical results.

Two details deserve attention beyond the headline. First, hand-written raw SQL ties compiled projections rather than beating them: once you stop over-fetching, EF Core’s generated SQL is competitive, and the usual argument for dropping to raw SQL for performance largely evaporates. Second, lazy loading’s standard deviation is enormous — 95,134 μs against a 336.9 ms mean. N+1 is not merely slow, it is unpredictable, which is why it tends to surface as an intermittent production incident rather than a steady, easily-noticed regression.

The hierarchy that emerges from the data

Put the two studies side by side and a priority order falls out that most performance discussions get backwards:

Query patterns are worth roughly 140x. Framework choice is worth roughly 1.2x.

These are not comparable line items, and yet framework selection consumes vastly more architectural debate than data-access review does in most teams we work with. A team that migrates from Controllers to FastEndpoints while leaving a lazy-loading N+1 in its hottest endpoint has optimized the wrong end of the stack by two orders of magnitude.

The practical sequence this suggests:

  1. Find the N+1s. Log generated SQL in a staging environment and look for query counts that scale with result count. This is nearly always the largest single win available.
  2. Project, do not materialize. If an endpoint returns a DTO, select into the DTO. Loading full entities to discard most of their columns costs allocation and time on every request.
  3. Compile the hot ones. Compiled queries cut the remaining overhead and, notably, cut variance — the standard deviation on compiled projections was the tightest in the set.
  4. Then, and only then, weigh the framework. On a greenfield service, take the 15–20% and the allocation savings. On a working codebase, price the migration honestly against that number.

Running these yourself

Every study ships with its benchmark project in a single open-source monorepo: github.com/Code-Majesty-Tech/dotnet-performance-guide. You need the .NET 10 SDK and Docker — PostgreSQL is provisioned through Testcontainers, so there is no manual database setup — and then dotnet run -c Release from the relevant benchmark directory.

We publish the source for a specific reason. Benchmark numbers are a function of hardware, data shape, and workload, and ours are not yours. What transfers between environments is the ranking and the order of magnitude, not the absolute milliseconds. If you run these and get materially different results, that is a finding worth having, and it is the kind of thing we are glad to be told about.

What is published and what is planned

This series is in progress, and this page is deliberately honest about where it stands. Two studies are published: API framework overhead and EF Core N+1 query patterns. Further studies are planned across caching strategies, JSON serialization, EF Core versus Dapper, and output caching.

New posts join the list below automatically as they are published, so this page is worth bookmarking rather than re-finding. We would rather ship two benchmarks we actually ran than announce ten we have only outlined.

Where this comes from

These benchmarks are a byproduct of consulting work, not a content exercise. The scenarios chosen are the ones that keep appearing in client codebases: an endpoint that got slow after the dataset grew, a service that allocates more than it should, a query that behaves fine in development and badly under real row counts.

That origin is also the bias to be aware of. We use FastEndpoints in production, which is why the write-path scenario where it loses is reported rather than omitted — a benchmark series that never contradicts the author’s preferences is marketing wearing a lab coat.

Conclusion

The two published studies point the same direction: the performance decisions that get the most architectural attention are rarely the ones with the most leverage. Framework choice is real and worth taking when it is free, but it is a 1.2x decision. How you shape your queries is a 140x decision, and it is usually available without changing a single dependency.

Start with the data access, measure before and after on your own hardware, and treat any number you did not run yourself — including the ones on this page — as a hypothesis rather than a fact.

Frequently asked questions

Why do these numbers disagree with the official framework benchmarks?

Because they measure a different thing. Vendor benchmarks typically hammer an empty endpoint with a load generator — no database, no validation, no real DTO serialization. That tells you how fast a framework can return '200 OK' to a request that does nothing. Our benchmarks put the same framework in front of a real workload: deserialize a body, validate it, query PostgreSQL through EF Core, serialize a response. Framework overhead measured on top of real work is much smaller in relative terms, and that is the number you can actually plan capacity with.

What is the single highest-leverage performance fix in a typical .NET API?

The data says query patterns, not framework choice — and it is not close. Switching from MVC Controllers to FastEndpoints bought roughly 15–20% on request latency. Replacing an N+1 lazy-loading pattern with a compiled projection bought about 140x on the same PostgreSQL workload. If you have limited time, profile your data access first. The framework is the foundation; the queries are the building.

Can I run these benchmarks myself?

Yes, and you should. Every post in the series ships with a runnable BenchmarkDotNet project in a shared open-source monorepo at github.com/Code-Majesty-Tech/dotnet-performance-guide. Each requires the .NET 10 SDK and Docker (PostgreSQL is provisioned via Testcontainers, so there is nothing to install by hand). Run `dotnet run -c Release` from the benchmark project directory. If your numbers differ from ours, your hardware and workload differ from ours — which is exactly why the source is published.

Is this series finished?

No. Two posts are published — API frameworks and EF Core N+1 query patterns. More are planned across caching, serialization, Dapper comparisons, and output caching. This page is the hub, and newly published posts appear in the list below automatically. We would rather publish two studies we actually ran than ten we outlined.

Do these results apply to .NET 8 or .NET 9?

Partially. The relative ordering of approaches — projection beating eager loading beating lazy loading, Minimal-API-derived frameworks beating the full MVC pipeline — has been stable across recent runtimes. The absolute numbers have not; each release has moved allocation and throughput baselines. Treat the ranking as portable and the magnitudes as specific to .NET 10 on the stated hardware.

Deep dives in this cluster

2 posts sharing this guide's themes.