Skip to content

Commit

Permalink
Add documentation for Compiled models
Browse files Browse the repository at this point in the history
  • Loading branch information
AndriySvyryd committed Oct 22, 2021
1 parent 9112d48 commit 6ec2674
Show file tree
Hide file tree
Showing 6 changed files with 260 additions and 88 deletions.
8 changes: 7 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,11 @@
"savepoints",
"serializable",
"subquery"
]
],
"markdownlint.config": {
"MD028": false,
"MD025": {
"front_matter_title": ""
}
}
}
89 changes: 58 additions & 31 deletions entity-framework/core/cli/dotnet.md

Large diffs are not rendered by default.

125 changes: 76 additions & 49 deletions entity-framework/core/cli/powershell.md

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion entity-framework/core/managing-schemas/ensure-created.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
title: Create and Drop APIs - EF Core
description: APIs for creating and dropping databases with Entity Framework Core
author: bricelam
ms.date: 11/07/2018
ms.date: 10/21/2021
no-loc: [EnsureDeleted, EnsureCreated]
uid: core/managing-schemas/ensure-created
---
# Create and Drop APIs
Expand Down
119 changes: 115 additions & 4 deletions entity-framework/core/performance/advanced-performance-topics.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: Advanced Performance Topics
description: Advanced performance topics for Entity Framework Core
author: rick-anderson
ms.author: riande
ms.date: 12/9/2020
ms.date: 10/21/2021
uid: core/performance/advanced-performance-topics
---
# Advanced Performance Topics
Expand Down Expand Up @@ -128,15 +128,126 @@ Even if the sub-millisecond difference seems small, keep in mind that the consta
> [!NOTE]
> Avoid constructing queries with the expression tree API unless you really need to. Aside from the API's complexity, it's very easy to inadvertently cause significant performance issues when using them.
## Compiled models

> [!NOTE]
> Compiled models were introduced in EF Core 6.0.
Compiled models can improve EF Core startup time for applications with large models. A large model typically means hundreds to thousands of entity types and relationships. Startup time here is the time to perform the first operation on a `DbContext` when that `DbContext` type is used for the first time in the application. Note that just creating a `DbContext` instance does not cause the EF model to be initialized. Instead, typical first operations that cause the model to be initialized include calling `DbContext.Add` or executing the first query.

Compiled models are created using the `dotnet ef` command-line tool. Ensure that you have [installed the latest version of the tool](xref:core/cli/dotnet#installing-the-tools) before continuing.

A new `dbcontext optimize` command is used to generate the compiled model. For example:

```dotnetcli
dotnet ef dbcontext optimize
```

The `--output-dir` and `--namespace` options can be used to specify the directory and namespace into which the compiled model will be generated. For example:

```dotnetcli
PS C:\dotnet\efdocs\samples\core\Miscellaneous\CompiledModels> dotnet ef dbcontext optimize --output-dir MyCompiledModels --namespace MyCompiledModels
Build started...
Build succeeded.
Successfully generated a compiled model, to use it call 'options.UseModel(MyCompiledModels.BlogsContextModel.Instance)'. Run this command again when the model is modified.
PS C:\dotnet\efdocs\samples\core\Miscellaneous\CompiledModels>
```

* For more information see [`dotnet ef dbcontext optimize`](xref:core/cli/dotnet#dotnet-ef-dbcontext-optimize).
* If you're more comfortable working inside Visual Studio, you can also use [Optimize-DbContext](../cli/powershell.md#optimize-dbcontext)

The output from running this command includes a piece of code to copy-and-paste into your `DbContext` configuration to cause EF Core to use the compiled model. For example:

```csharp
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseModel(MyCompiledModels.BlogsContextModel.Instance)
.UseSqlite(@"Data Source=test.db");
```

### Compiled model bootstrapping

It is typically not necessary to look at the generated bootstrapping code. However, sometimes it can be useful to customize the model or its loading. The bootstrapping code looks something like this:

<!--
[DbContext(typeof(BlogsContext))]
partial class BlogsContextModel : RuntimeModel
{
private static BlogsContextModel _instance;
public static IModel Instance
{
get
{
if (_instance == null)
{
_instance = new BlogsContextModel();
_instance.Initialize();
_instance.Customize();
}
return _instance;
}
}
partial void Initialize();
partial void Customize();
}
-->
[!code-csharp[RuntimeModel](../../../samples/core/Miscellaneous/CompiledModels/SingleRuntimeModel.cs?name=RuntimeModel)]

This is a partial class with partial methods that can be implemented to customize the model as needed.

In addition, multiple compiled models can be generated for `DbContext` types that may use different models depending on some runtime configuration. These should be placed into different folders and namespaces, as shown above. Runtime information, such as the connection string, can then be examined and the correct model returned as needed. For example:

<!--
public static class RuntimeModelCache
{
private static readonly ConcurrentDictionary<string, IModel> _runtimeModels
= new();
public static IModel GetOrCreateModel(string connectionString)
=> _runtimeModels.GetOrAdd(
connectionString, cs =>
{
if (cs.Contains("X"))
{
return BlogsContextModel1.Instance;
}
if (cs.Contains("Y"))
{
return BlogsContextModel2.Instance;
}
throw new InvalidOperationException("No appropriate compiled model found.");
});
}
-->
[!code-csharp[RuntimeModelCache](../../../samples/core/Miscellaneous/CompiledModels/MultipleRuntimeModels.cs?name=RuntimeModelCache)]

### Limitations

Compiled models have some limitations:

* [Global query filters are not supported](https://github.com/dotnet/efcore/issues/24897).
* [Lazy loading and change-tracking proxies are not supported](https://github.com/dotnet/efcore/issues/24902).
* [The model must be manually synchronized by regenerating it any time the model definition or configuration change](https://github.com/dotnet/efcore/issues/24894).
* Custom IModelCacheKeyFactory implementations are not supported. However, you can compile multiple models and load the appropriate one as needed.

Because of these limitations, you should only use compiled models if your EF Core startup time is too slow. Compiling small models is typically not worth it.

If supporting any of these features is critical to your success, then please vote for the appropriate issues linked above.

## Reducing runtime overhead

As with any layer, EF Core adds a bit of runtime overhead compared to coding directly against lower-level database APIs. This runtime overhead is unlikely to impact most real-world applications in a significant way; the other topics in this performance guide, such as query efficiency, index usage and minimizing roundtrips, are far more important. In addition, even for highly-optimized applications, network latency and database I/O will usually dominate any time spent inside EF Core itself. However, for high-performance, low-latency applications where every bit of perf is important, the following recommendations can be used to reduce EF Core overhead to a minimum:

* Turn on [DbContext pooling](#dbcontext-pooling); our benchmarks show that this feature can have a decisive impact on high-perf, low-latency applications.
* Make sure that the `maxPoolSize` corresponds to your usage scenario; if it is too low, DbContext instances will be constantly created and disposed, degrading performance. Setting it too high may needlessly consume memory as unused DbContext instances are maintained in the pool.
* For an extra tiny perf boost, consider using `PooledDbContextFactory` instead of having DI inject context instances directly (EF Core 6 and above). DI management of DbContext pooling incurs a slight overhead.
* Make sure that the `maxPoolSize` corresponds to your usage scenario; if it is too low, `DbContext` instances will be constantly created and disposed, degrading performance. Setting it too high may needlessly consume memory as unused `DbContext` instances are maintained in the pool.
* For an extra tiny perf boost, consider using `PooledDbContextFactory` instead of having DI inject context instances directly (EF Core 6 and above). DI management of `DbContext` pooling incurs a slight overhead.
* Use precompiled queries for hot queries.
* The more complex the LINQ query - the more operators it contains and the bigger the resulting expression tree - the more gains can be expected from using compiled queries.
* Consider disabling thread safety checks by setting `EnableThreadSafetyChecks` to false in your context configuration (EF Core 6 and above).
* Using the same DbContext instance concurrently from different threads isn't supported. EF Core has a safety feature which detects this programming bug in many cases (but not all), and immediately throws an informative exception. However, this safety feature adds some runtime overhead.
* Using the same `DbContext` instance concurrently from different threads isn't supported. EF Core has a safety feature which detects this programming bug in many cases (but not all), and immediately throws an informative exception. However, this safety feature adds some runtime overhead.
* **WARNING:** Only disable thread safety checks after thoroughly testing that your application doesn't contain such concurrency bugs.
4 changes: 2 additions & 2 deletions entity-framework/core/what-is-new/ef-core-6.0/whatsnew.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ EF Core supports querying historical data from the table through several new que
* `TemporalBetween`: The same as `TemporalFromTo`, except that rows are included that became active on the upper boundary.
* `TemporalContainedIn`: : Returns all rows that started being active and ended being active between two given UTC times. This may be many rows from the history table for a given primary key.

> [!INFO]
> [!NOTE]
> See the [SQL Server temporal tables documentation](/sql/relational-databases/tables/temporal-tables#how-do-i-query-temporal-data)] for more information on exactly which rows are included for each of these operators.
For example, after making some updates and deletes to our data, we can run a query using `TemporalAll` to see the historical data:
Expand Down Expand Up @@ -686,7 +686,7 @@ If supporting any of these features is critical to your success, then please vot
The model in the GitHub repo referenced above contains 449 entity types, 6390 properties, and 720 relationships. This is a moderately large model. Using [BenchmarkDotNet](https://www.nuget.org/packages/BenchmarkDotNet) to measure, the average time to first query is 1.02 seconds on a reasonably powerful laptop. Using compiled models brings this down to 117 milliseconds on the same hardware. An 8x to 10x improvement like this stays relatively constant as the model size increases.

![Add Connection LocalDB](compiled_models.png)
![Compiled model performance improvement](compiled_models.png)

> [!NOTE]
> See [Announcing Entity Framework Core 6.0 Preview 5: Compiled Models](https://devblogs.microsoft.com/dotnet/announcing-entity-framework-core-6-0-preview-5-compiled-models/) on the .NET Blog for a more in-depth discussion of EF Core startup performance and compiled models.
Expand Down

0 comments on commit 6ec2674

Please sign in to comment.