Entity Framework Core (EF Core) makes database development easier by allowing .NET developers to work with entities, LINQ queries, and DbContext classes instead of writing SQL for every operation. However, as an application grows, its data model can become large. It may contain hundreds of entity types, relationships, configurations, and mappings.
Building and initializing this model can add overhead when an application starts using a particular DbContext model for the first time. This can matter in applications that start frequently, such as serverless workloads, short-lived background processes, and services that scale out by creating new instances.
EF Core provides compiled models to reduce some of this model initialization work. Instead of building the model dynamically at runtime, developers can generate code that represents the model ahead of time.
But compiled models are not a universal performance fix. They introduce additional generated code and maintenance considerations, and they do not automatically make database queries faster.
In this article, you will learn how EF Core compiled models work, how to generate and configure one, when it can help, and how to measure whether it improves your application.
What Are EF Core Compiled Models?
EF Core builds a model that describes how application classes map to database structures. The model includes entity types, properties, relationships, keys, and other configuration.
Consider a simple application model:
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
}
A corresponding DbContext might look like this:
using Microsoft.EntityFrameworkCore;
public class AppDbContext : DbContext
{
public AppDbContext(
DbContextOptions<AppDbContext> options)
: base(options)
{
}
public DbSet<Product> Products => Set<Product>();
}
When EF Core initializes the model, it examines the context configuration and determines how the entity maps to the database.
For many applications, this process is fast enough that developers do not need to optimize it.
However, a large model can take longer to initialize. If model initialization becomes a measurable part of application startup, a compiled model may help.
A compiled model is generated code that represents the EF Core model. EF Core can use this generated representation instead of constructing the model through its normal runtime model-building process.
The key distinction is that compiled models target model initialization. They are not the same as compiled LINQ queries, and they do not automatically optimize every database operation.
When Should You Consider Compiled Models?
Compiled models are most useful when model initialization is a meaningful part of startup time.
Consider these scenarios:
Application scenario | Potential benefit |
|---|
Large domain model with many entity types | May reduce model initialization overhead |
Short-lived background processes | May reduce the time before useful work begins |
Serverless applications | May help where startup overhead affects execution time |
Frequently restarted services | May reduce repeated initialization costs |
Small CRUD application | Benefits may be too small to justify the additional setup |
Application dominated by slow database queries | May have little impact on overall response time |
The size of the model is an important factor, but it is not the only one. Configuration complexity, startup behavior, application lifetime, and the EF Core version can all affect the result.
Measure your application before making the change. If startup time is already low, compiled models may add complexity without delivering a meaningful improvement.
Step 1: Check Your EF Core Version
Before generating a compiled model, confirm which EF Core version your application uses.
You can inspect the package references in your project file:
<ItemGroup>
<PackageReference
Include="Microsoft.EntityFrameworkCore"
Version="10.0.0" />
<PackageReference
Include="Microsoft.EntityFrameworkCore.SqlServer"
Version="10.0.0" />
</ItemGroup>
This is an illustrative package configuration. Use package versions compatible with your application's target framework and deployment requirements.
The EF Core command-line tools should also be compatible with the EF Core packages used by your project.
If the tools are not installed, you can install the dotnet-ef tool:
dotnet tool install --global dotnet-ef
If it is already installed, check its version:
dotnet ef --version
For a project that requires a specific tool version, use a local tool manifest and pin the version approved by your team. This makes development and CI environments more predictable.
Step 2: Generate the Compiled Model
EF Core provides a command-line option for generating compiled models.
From the directory containing your project, run:
dotnet ef dbcontext optimize
The command generates code representing the model for the selected DbContext.
If your project contains multiple contexts, specify the one you want to optimize:
dotnet ef dbcontext optimize \
--context AppDbContext
On Windows Command Prompt, you can enter the command on one line:
dotnet ef dbcontext optimize --context AppDbContext
The generated files are placed in the output directory configured by the command or its defaults. Review the command output to identify the generated namespace and model class.
You can specify a namespace and output directory explicitly:
dotnet ef dbcontext optimize \
--context AppDbContext \
--output-dir CompiledModels \
--namespace MyApp.CompiledModels
The generated output is source code, not a replacement for your entity classes or DbContext.
Commit the generated files if your team's build and deployment process requires them to be part of the application source. Regenerate them whenever relevant model configuration changes.
Step 3: Configure the DbContext to Use the Compiled Model
Generating the files does not automatically make the application use them. You must configure the context to reference the generated model.
The exact generated class name and namespace depend on your project and tool output. Suppose the generated model class is named AppDbContextModel in the MyApp.CompiledModels namespace.
You can configure the context as follows:
using Microsoft.EntityFrameworkCore;
using MyApp.CompiledModels;
var builder = WebApplication.CreateBuilder(args);
var connectionString =
builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<AppDbContext>(options =>
{
options
.UseSqlServer(connectionString)
.UseModel(AppDbContextModel.Instance);
});
var app = builder.Build();
app.MapGet("/", () => "Application is running.");
app.Run();
Use the actual generated type and namespace from your output. If the generated class has a different name, update the reference accordingly.
The important part is:
.UseModel(AppDbContextModel.Instance)
This tells EF Core to use the supplied compiled model.
The example uses SQL Server. If your application uses another database provider, configure the appropriate provider instead.
After adding the compiled model, build and run the application. Verify that the context initializes correctly and that normal database operations still work.
Step 4: Understand What Compiled Models Do Not Optimize
Compiled models are easy to misunderstand because the word "compiled" appears in several EF Core features.
EF Core compiled models and compiled queries solve different problems.
Feature | Main purpose | Typical use |
|---|
Compiled models | Reduce model initialization work | Large models with measurable startup overhead |
Compiled queries | Reduce some query-processing overhead | Frequently executed queries where query compilation is a measured cost |
Database indexes | Improve access to data in suitable query patterns | Queries that need efficient filtering, joining, or sorting |
Connection pooling | Reuse database connections | Reduce connection setup overhead |
Context pooling | Reuse DbContext instances | Reduce context initialization and allocation overhead in suitable workloads |
A compiled model does not automatically create database indexes, reduce network latency, or improve an inefficient SQL query.
For example, consider this query:
var products = await dbContext.Products
.Where(product => product.Price > 100)
.OrderBy(product => product.Name)
.ToListAsync();
If this query is slow because SQL Server scans a large table, a compiled model is unlikely to fix the underlying database problem.
You may need to inspect the generated SQL, execution plan, indexes, and data distribution instead.
Similarly, if the application spends most of its time waiting for a remote database, reducing model initialization may have little effect on total request latency.
Choose the optimization that matches the actual bottleneck.
Step 5: Measure Startup Performance Before and After
Performance work should begin with a baseline.
Measure the time required to initialize the context and perform representative operations before adding a compiled model. Then repeat the measurements after enabling it.
For example, a simple diagnostic program can measure the time required to initialize a context and execute a query:
using System.Diagnostics;
using Microsoft.EntityFrameworkCore;
var stopwatch = Stopwatch.StartNew();
await using (var context = CreateDbContext())
{
await context.Database.CanConnectAsync();
var count = await context.Products.CountAsync();
Console.WriteLine($"Product count: {count}");
}
stopwatch.Stop();
Console.WriteLine(
$"Elapsed time: {stopwatch.ElapsedMilliseconds} ms");
The CreateDbContext() method represents the application's existing context-creation logic.
This example measures several operations together, including connection checks and a database query. It is useful as a basic diagnostic but does not isolate model initialization.
For a reliable comparison, use a benchmark that measures context and model initialization separately from database connectivity and query execution. Control for warm-up, caching, database load, and other environmental factors.
Also distinguish between cold startup and steady-state performance. A compiled model may improve the first-use path without changing the speed of queries after the application is fully initialized.
Record at least these measurements:
Time until the application is ready to serve requests.
Time required for the first context initialization.
First-query latency.
Steady-state request latency.
Memory usage and generated application size.
Repeat each test under comparable conditions. A single timing result is not enough to establish a meaningful improvement.
Step 6: Handle Model Changes Correctly
Compiled models introduce an additional maintenance step: generated model code must remain consistent with the application's model configuration.
Suppose you add a new property to an entity:
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
public bool IsActive { get; set; }
}
If the application uses a compiled model generated before the change, the generated model may not reflect the updated configuration.
Regenerate the compiled model after relevant changes to entities, relationships, or model-building configuration.
A practical workflow is:
Update the entity or model configuration.
Update database migrations if the database schema changes.
Regenerate the compiled model.
Build the application.
Run database integration tests.
Review the generated code changes.
Deploy the application and matching database changes through the normal release process.
Keep model generation reproducible. Use the same EF Core tooling version across developer machines and CI where possible.
If a model contains features that are unsupported by compiled models, generation may fail or require changes. Check the limitations for the EF Core version you use before adopting the feature.
Common Mistakes to Avoid
Expecting every query to become faster
Compiled models target model initialization. They do not automatically optimize query execution, database indexes, or network operations.
Generating the model only once
Generated code can become outdated as the model evolves. Include regeneration in the development process and verify that the output matches the current model.
Measuring only total request time
A slow request may spend most of its time on database access or external services. Measure startup and query execution separately before deciding whether compiled models are appropriate.
Ignoring generated code size
Large models can produce substantial generated source code. Consider build time, assembly size, and maintenance costs along with runtime improvements.
Using incompatible tooling versions
Mismatched EF Core tools and runtime packages can cause generation problems. Keep versions aligned with the project's dependency configuration.
Best Practices for EF Core Compiled Models
Start by profiling the application and confirming that model initialization is a meaningful performance cost. If it is not, focus on more relevant bottlenecks.
When compiled models are appropriate, generate them using tooling compatible with the project's EF Core version. Configure the correct generated model type, and verify that the application still performs its normal database operations.
Regenerate the model after relevant changes and run integration tests as part of CI. Compare cold-start performance, steady-state latency, memory usage, and build overhead before deciding whether to keep the optimization.
Finally, document the generation command so that developers and automated builds follow the same process.
Summary
EF Core compiled models can reduce model initialization overhead by providing generated code that represents the application's data model. They are worth evaluating when a large model contributes measurably to application startup time.
The process involves checking your EF Core version, generating the compiled model, configuring the DbContext to use it, and testing the application. You must also regenerate the model when relevant entity or configuration changes occur.
Compiled models do not replace query optimization, database indexing, or connection management. Measure the actual bottleneck and compare results before and after the change.
For applications where model initialization is a significant cost, compiled models can be a useful optimization. For applications dominated by database queries or network latency, other improvements may deliver greater value.
Join the conversation! Your thoughts help the community grow.