Chapter 17 · Performance Engineering: Query Shape, Compiled Artifacts, Pooling, and Database Evidence
Compiled Models, Startup Cost, Large Model Optimization, and NativeAOT Considerations
Generate compiled models for cold-start experiments, understand regeneration and feature constraints, and treat NativeAOT/query precompilation as an explicitly experimental EF Core 10 path rather than a silent production default.
Learning outcomes
Distinguish model-building cold-start cost from query compilation, database execution, and steady-state request cost.
Generate and wire a compiled ServiceHubContext model with dotnet ef dbcontext optimize.
Regenerate compiled artifacts whenever model/configuration changes and verify that CI detects stale generated model code.
Know the documented compiled-model feature limits before adopting the optimization.
Treat EF Core 10 NativeAOT and query precompilation as experimental and provider-dependent.
Measure cold-start improvement before accepting generated-code size and build/deployment complexity.
1. A compiled model optimizes first-use model initialization
The first meaningful operation on a DbContext type
causes EF to build and initialize its runtime model. For a small
model the cost is usually insignificant. Microsoft describes
compiled models as an optimization for large models—often
hundreds to thousands of entity types and relationships—where
cold-start becomes measurable.
Mandatory baseline: .NET runtime 10.0.11, SDK 10.0.400, Microsoft.EntityFrameworkCore / Design / Sqlite and dotnet-ef 10.0.11. SQLite is the free local database. BenchmarkDotNet 0.15.8 is an optional free benchmark harness; the mandatory labs can run with a controlled Release-mode console harness. EF Core 11 preview behavior is not part of this course baseline.
Do not confuse three separate artifacts: a compiled model is generated C# for EF model initialization; an explicit compiled query is a delegate for one LINQ shape; precompiled queries are generated interceptors used by EF’s experimental NativeAOT/query-precompilation pipeline.
2. Measure ServiceHub before generating anything
// Run each candidate in a fresh process for meaningful cold-start evidence.var sw = Stopwatch.StartNew();await using var db = factory.CreateDbContext();var first = await db.WorkOrders .AsNoTracking() .OrderBy(w => w.Id) .Select(w => w.Id) .FirstOrDefaultAsync(cancellationToken);sw.Stop();Console.WriteLine($"first-operation-ms={sw.Elapsed.TotalMilliseconds:F3}; id={first}");
A loop in one process measures warm reuse after the model is already initialized. For a cold-start comparison, launch a fresh Release build repeatedly and disclose filesystem/database cache state. The current ServiceHub model is intentionally much smaller than the extreme models for which compiled models are designed, so “no meaningful benefit” is a valid result.
3. Generate the compiled model with the pinned EF tool
dotnet tool run dotnet-ef dbcontext optimize --context ServiceHubContext --output-dir CompiledModels --namespace ServiceHub.EfLab.CompiledModels# Inspect and commit generated code only if the measured optimization is adopted.git diff -- CompiledModels
The command prints the generated model type to wire into
options. For this context the typical name is
ServiceHubContextModel.Instance; use the name
emitted by your actual command rather than copying it blindly.
services.AddDbContextFactory<ServiceHubContext>(options =>{ options.UseSqlite(connectionString); options.UseModel(ServiceHub.EfLab.CompiledModels.ServiceHubContextModel.Instance);});
4. Generated model code is a build artifact with a synchronization contract
Any change to entity configuration, converters, owned/complex
mapping, query filters, relationships, or provider metadata can
make a compiled model stale. The fix is not to hand-edit
generated runtime-model code; regenerate it. Add CI that runs
dbcontext optimize into a clean directory and fails
when the checked-in generated output differs.
rm -rf CompiledModels.generateddotnet tool run dotnet-ef dbcontext optimize --context ServiceHubContext --output-dir CompiledModels.generated --namespace ServiceHub.EfLab.CompiledModelsdiff -ru CompiledModels CompiledModels.generated
5. Check documented feature limits before adoption
| Compiled-model constraint | ServiceHub consequence |
|---|---|
| Global query filters unsupported | Chapter 22 introduces multi-tenancy filters; a later adoption decision must re-evaluate compiled models. |
| Lazy-loading/change-tracking proxies unsupported | Chapter 10 proxy lab is optional and separate; do not combine it with a compiled-model context. |
| Private-method value converters unsupported | Chapter 06 converters must reference public/internal methods if used by the compiled model. |
| Custom IModelCacheKeyFactory unsupported | Do not rely on per-request dynamic model variants. |
| Manual regeneration required | Any model/configuration change must regenerate generated code. |
These are not theoretical footnotes. If a required feature conflicts with compiled models, choose correctness/model clarity over a startup optimization.
6. Failure case: change the model but keep stale generated code
Suppose a developer adds an index or remaps a column and forgets
to run dbcontext optimize. The application can
build against generated model code representing an older shape.
The safe repair is a generation step tied to model changes and a
smoke test that compares expected metadata.
await using var db = await factory.CreateDbContextAsync(ct);var entity = db.Model.FindEntityType(typeof(WorkOrder)) ?? throw new InvalidOperationException("WorkOrder missing from model.");var number = entity.FindProperty(nameof(WorkOrder.WorkOrderNumber)) ?? throw new InvalidOperationException("WorkOrderNumber missing.");Console.WriteLine($"column={number.GetColumnName()}; max={number.GetMaxLength()}");
7. Precompiled queries and NativeAOT: current EF Core 10 status
As of this chapter’s August 2026 review, Microsoft still labels EF NativeAOT support, precompiled queries, and the related MSBuild integration as experimental and not suited for production. The mechanism statically finds queries and generates interceptors; dynamic queries and other patterns have limitations, and provider support must be checked.
# Experimental EF Core 10 learning path; not the mandatory production lab.dotnet tool run dotnet-ef dbcontext optimize --context ServiceHubContext --precompile-queries# NativeAOT adds additional generated code and provider requirements:dotnet tool run dotnet-ef dbcontext optimize --context ServiceHubContext --precompile-queries --nativeaot
Do not publish a NativeAOT recommendation by copying a command that succeeds locally. Verify the current EF release documentation, provider support, trimming/AOT warnings, dynamic-query requirements, and deployment target at the time of release.
8. Lab: make the adoption decision from cold-start evidence
- Run the non-compiled model executable in Release mode in a fresh process at least 20 times and save raw first-operation times.
- Generate and wire the compiled model, rebuild, and repeat under the same conditions.
- Record generated code size and build-time change.
- Change one harmless mapping in a throwaway branch, demonstrate the regeneration requirement, then revert.
- Check the course model for features on the documented unsupported list.
- Write an adoption decision: keep, reject, or defer. “Reject because the model is small and the gain is noise” is an acceptable outcome.
9. Production judgment
Compiled models trade generated-code/build complexity for first-use model initialization speed. They do not make SQL plans faster, reduce row counts, or fix network latency. Use them when cold-start is an actual service-level problem and the model is large enough to justify the complexity. The next lesson tackles another often-confused optimization family: context pooling versus driver connection pooling.
Check your understanding
- What phase does a compiled model target?
- Does creating a DbContext necessarily initialize the model?
- What must happen after model configuration changes?
- Name one documented compiled-model incompatibility.
- What is the current course stance on NativeAOT/precompiled queries?
- What is a valid outcome of the cold-start lab?
Review the answers
1. First-use EF model initialization, especially for large models.
2. No. Typical first operations such as a query or Add trigger model initialization.
3. Regenerate the compiled model and validate the generated artifact.
4. Examples include global query filters, lazy-loading/change-tracking proxies, custom IModelCacheKeyFactory, or private-method value converters.
5. Optional experimental learning only; Microsoft still warns they are not suited for production in the current documentation.
6. Rejecting compiled models because ServiceHub is too small for a material measured gain.
Authoritative references
Performance behavior is workload-, provider-, and version-sensitive. Re-check these primary sources before carrying a result into production.
- Advanced Performance Topics - compiled models — compiled-model command, use and limitations
- NativeAOT Support and Precompiled Queries — experimental status and limitations
- EF Core MSBuild tasks — experimental build/publish integration for compiled models/precompiled queries
- EF Core .NET CLI tools — dbcontext optimize options
- What’s New in EF Core 10 — stable EF Core 10 LTS baseline