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.

Advanced150–190 minutescompiled-model cold-start labEF Core 10.0.11 · .NET 10.0.11SQLite provider 10.0.11 mandatory baselinedotnet-ef 10.0.11 · SDK 10.0.400Last reviewed: August 2026

Learning outcomes

01

Distinguish model-building cold-start cost from query compilation, database execution, and steady-state request cost.

02

Generate and wire a compiled ServiceHubContext model with dotnet ef dbcontext optimize.

03

Regenerate compiled artifacts whenever model/configuration changes and verify that CI detects stale generated model code.

04

Know the documented compiled-model feature limits before adopting the optimization.

05

Treat EF Core 10 NativeAOT and query precompilation as experimental and provider-dependent.

06

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.

Frozen lab baseline

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

csharp · cold-start harness concept
// 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

shell · generate ServiceHub compiled model
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.

csharp · wire the generated model
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.

shell · CI drift check sketch
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.

csharp · metadata smoke test
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.

shell · optional experimental inspection only
# 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
Feature status matters

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

  1. Run the non-compiled model executable in Release mode in a fresh process at least 20 times and save raw first-operation times.
  2. Generate and wire the compiled model, rebuild, and repeat under the same conditions.
  3. Record generated code size and build-time change.
  4. Change one harmless mapping in a throwaway branch, demonstrate the regeneration requirement, then revert.
  5. Check the course model for features on the documented unsupported list.
  6. 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

  1. What phase does a compiled model target?
  2. Does creating a DbContext necessarily initialize the model?
  3. What must happen after model configuration changes?
  4. Name one documented compiled-model incompatibility.
  5. What is the current course stance on NativeAOT/precompiled queries?
  6. 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.

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.

\n