Today, the Entity Framework Core team announces the fifth preview release of EF Core 6.0. This release includes the first iteration of compiled models. If startup time for your application is important and your EF Core model contains hundreds or thousands of entities, properties, and relationships, this is one release you don’t want to ignore.
TL;DR;
- Compiled models dramatically reduce startup time for your application.
- The models are generated (similar to how migrations are) so they should be refreshed whenever your model changes.
- Some features are not currently supported by compiled models, so be aware of the limitations when you try them out.
Background
How does 10x performance sound to you? Our team created a sample project with a DbContext
that contains 449 entity types, 6,390 properties and 720 relationships. I wrote a console app that loops several times, creates a new instance of a DbContext
and loads a set of entities with no filters or ordering. The start-up time for the first run consistently takes around two seconds on my laptop, with subsequent cached instances weighing in at about 1.5 seconds. Here’s the output from a run:
$ dotnet run -c Release
Model has:
449 entity types
6390 properties
720 relationships
Instantiating context...
It took 00:00:02.1603163.
Instantiating context...
It took 00:00:01.6268628.
Instantiating context...
It took 00:00:01.7144346.
Instantiating context...
It took 00:00:01.6090380.
Instantiating context...
It took 00:00:01.7049987.
After testing the baseline application, I used the new EF Core tools Command Line Interface (CLI) feature to optimize the DbContext
:
dotnet ef dbcontext optimize -output-dir MyCompiledModels --namespace MyCompiledModels
The tool gave me instructions to add a single line of code to my DbContext
configuration:
options.UseModel(MyCompiledModels.BlogsContextModel.Instance);
I made the update and re-ran the code to receive a 10x performance gain with the initial model taking 257ms to complete. The cached model reduced additional calls to just 10ms.
$ dotnet run -c Release
Model has:
449 entity types
6390 properties
720 relationships
Instantiating context...
It took 00:00:00.2573627.
Instantiating context...
It took 00:00:00.0132345.
Instantiating context...
It took 00:00:00.0119556.
Instantiating context...
It took 00:00:00.0101717.
Instantiating context...
It took 00:00:00.0139057.
A peek at the query pipeline
EF Core performs quite a bit of work to get from your application to returning the first result of the first query your application processes. Let’s break down the following two statements and go “behind the scenes” to see what happens.
using var myContext = new MyContext();
var results = myContext.MyWidgets.ToList();
DbContext instantiation
The first step is creating an instance of the context. The first time a DbContext
is created, EF Core will create and compile delegates to set the table properties you expose by using DbSet<Entity>
. This simply creates the delegates to set the properties so you can query them right away.
Performance tip: you can avoid the overhead of
DbSet
initialization by using an alternate approach such as thecontext.Set<Entity>()
API call.
DbContext (lazy) initialization
After the DbContext
is created, EF Core “goes to sleep” until you use it. The first time you use a context by accessing one of its APIs (such as navigating an entity and returning results), the context is initialized. This will run the OnConfiguring
method to establish the proper provider and database connections as well as other settings. For example, this is the perfect place to use the simple logging feature by calling the new LogTo
extension on the options builder.
Service provider
EF Core uses a service-based architecture and has an internal dependency injection framework. This provider is built internally but is designed to work with external DI solutions such as the service provider in ASP.NET Core.
Performance tip: much of the overhead described so far can be mitigated by using context pooling. This enables a pool of reusable context instances that are already initialized.
Model building
To understand how a domain object (C# class) relates to the tables and relationships in the database, EF Core builds an internal model that represents all the types, properties, constraints, and relationships that it finds in your DbContext
. This is a metadata model and includes the call to OnModelCreating
that can be overridden to provide fluent configuration of the model.
Query compilation
A major reason why developers use EF Core is its ability to parse Language Integrated Queries (LINQ) into the database dialect. This is an advanced stage because it involves traversing a potentially complex expression tree and translating it into SQL. Something trivial like a projection:
var projection = myQuery.Select(obj => new { id = obj.EntityId, name = obj.Identifier });
Seems easy enough to translate:
SELECT EntityId, Identifier FROM ...
But what about something more complicated, like this?
var pairs = (from a1 in context.Attendees
from a2 in context.Attendees
where a1.Id != a2.Id
select new
{
a1 = a1.Id,
a1LastName = a1.LastName,
a1FirstName = a2.FirstName,
a2 = a2.Id,
a2LastName = a2.LastName,
a2FirstName = a2.FirstName,
sessionCount =
a1.Sessions.Select(s => s.Id)
.Intersect(a2.Sessions.Select(s => s.Id)).Count()
}).OrderByDescending(shared => shared.sessionCount)
.Take(5);
This is ultimately parsed into native SQL, intersection and all. The first time that EF Core encounters a query, it parses the query to determine which parts are dynamic. It then compiles the static parts of the query and parameterizes the dynamic aspects to expedite translation into SQL by using a SQL template.
Run the query
Finally! The query is now run. To avoid the overhead of performing these steps every time, EF Core caches the delegates for DbSet
properties, the internal service provider, the constructed model, and the compiled query. This results in much faster performance after the queries are successfully run the first time.
You can visualize these steps using the following diagram (note the cache boxes have strike-through to show they are disabled for our benchmark tests):
Although most of the pipeline is already streamlined, model compilation was an area we knew could improve.
A note on source generators. The approach the team chose is to provide a command that generates the source code files that you can then incorporate into your project to build the compiled model. We are often asked why we didn’t choose source generators. The answer is that source generators run as user code inside the Visual Studio process. EF Core must build and run the context to obtain information about the model. If an exception is thrown as part of the process, this could potentially force Visual Studio to hang or crash.
As with most technology, compiled models do have trade-offs. Let’s look at the pros and cons.
Pros and cons
The pros should be clear. As your model grows larger, your startup time remains fast. Here is a comparison of startup time between compiled and non-compiled models based on the size of the model.
Here are some cons to consider:
- Global query filters are not supported.
- Lazy loading proxies are not supported.
- Change tracking proxies are not supported.
- Custom IModelCacheKeyFactory implementations are not supported.
- The model must be manually synchronized by regenerating it any time the model definition or configuration change.
Tip: if supporting any of these features is critical to your success, please find the issue and upvote it or add your comments and thoughts, or file a new issue to let us know.
Now you’ve learned the background. How do you get started?
In conclusion
To start using compiled models today, reap the performance benefits and have the opportunity to provide us with feedback before we release the final EF Core 6.0 version, start by grabbing the latest preview (instructions are below) and installing the latest EF Core CLI. The new tool command looks like this (all parameters are optional):
dotnet ef dbcontext optimize -c MyContext -o MyFolder -n My.Namespace
Inside the NuGet package manager console you can use this:
Optimize-DbContext -Context MyContext -OutputDir MyFolder -Namespace My.Namespace
The tool will instruct you to add a line like this to your options configuration:
opts.UseModel(My.Namespace.MyContextModel.Instance);
We hope you benefit from this new feature and can provide us with early feedback. Check out the EF Core 6.0 plan. In addition to other work, the team has prioritized a number of Azure Cosmos DB provider features. Please upvote the features that are important to you and share any feedback you may have! Other features in the preview 5 release will be posted in EF Core 6.0 What’s New.
How to get EF Core 6.0 previews
EF Core is distributed exclusively as a set of NuGet packages. For example, to add the SQL Server provider to your project, you can use the following command using the dotnet tool:
dotnet add package Microsoft.EntityFrameworkCore.SqlServer --version 6.0.0-preview.5.21301.9
This following table links to the preview 5 versions of the EF Core packages and describes what they are used for.
Package | Purpose |
---|---|
Microsoft.EntityFrameworkCore | The main EF Core package that is independent of specific database providers |
Microsoft.EntityFrameworkCore.SqlServer | Database provider for Microsoft SQL Server and SQL Azure |
Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite | SQL Server support for spatial types |
Microsoft.EntityFrameworkCore.Sqlite | Database provider for SQLite that includes the native binary for the database engine |
Microsoft.EntityFrameworkCore.Sqlite.Core | Database provider for SQLite without a packaged native binary |
Microsoft.EntityFrameworkCore.Sqlite.NetTopologySuite | SQLite support for spatial types |
Microsoft.EntityFrameworkCore.Cosmos | Database provider for Azure Cosmos DB |
Microsoft.EntityFrameworkCore.InMemory | The in-memory database provider |
Microsoft.EntityFrameworkCore.Tools | EF Core PowerShell commands for the Visual Studio Package Manager Console; use this to integrate tools like scaffolding and migrations with Visual Studio |
Microsoft.EntityFrameworkCore.Design | Shared design-time components for EF Core tools |
Microsoft.EntityFrameworkCore.Proxies | Lazy-loading and change-tracking proxies |
Microsoft.EntityFrameworkCore.Abstractions | Decoupled EF Core abstractions; use this for features like extended data annotations defined by EF Core |
Microsoft.EntityFrameworkCore.Relational | Shared EF Core components for relational database providers |
Microsoft.EntityFrameworkCore.Analyzers | C# analyzers for EF Core |
We also published the 6.0 preview 5 release of the Microsoft.Data.Sqlite.Core provider for ADO.NET.
Thank you from the team
A big thank you from the EF team to everyone who has used EF over the years!
Arthur Vickers | Andriy Svyryd | Brice Lambson | Jeremy Likness |
Maurycy Markowski | Shay Rojansky | Smit Patel |
Thank you to our contributors!
We are grateful to our amazing community of contributors. Our success is founded upon the shoulders of your efforts and feedback. If you are interested in contributing but not sure how or would like help, please reach out to us! We want to help you succeed. We would like to publicly acknowledge and thank these contributors for investing in the success of EF Core 6.0.
The post Announcing Entity Framework Core 6.0 Preview 5: Compiled Models appeared first on .NET Blog.
source https://devblogs.microsoft.com/dotnet/announcing-entity-framework-core-6-0-preview-5-compiled-models/
Comments
Post a Comment