Skip to content

Commit 13503b1

Browse files
committed
Documentation for NativeAOT/precompiled queries
Closes #3988
1 parent 84be865 commit 13503b1

File tree

3 files changed

+142
-16
lines changed

3 files changed

+142
-16
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
---
2+
title: NativeAOT Support and Precompiled Queries (Experimental) - EF Core
3+
description: Publishing NativeAOT Entity Framework Core applications and using precompiled queries
4+
author: roji
5+
ms.date: 11/10/2024
6+
uid: core/miscellaneous/nativeaot-and-precompiled-queries
7+
---
8+
# NativeAOT Support and Precompiled Queries (Experimental)
9+
10+
> [!WARNING]
11+
> NativeAOT and query precompilation are highly experimental feature, and are not yet suited for production use. The support described below should be viewed as infrastructure towards the final feature, which will likely be released with EF 10. We encourage you to experiment with the current support and report on your experiences, but recommend against deploying EF NativeAOT applications in production. See below for specific known limitations.
12+
13+
[.NET NativeAOT](/dotnet/core/deploying/native-aot) allows publishing self-contained .NET applications that have been compiled ahead-of-time (AOT). Doing so offers the following advantages:
14+
15+
* Significantly faster application startup time
16+
* Small, self-contained binaries that have smaller memory footprints and are easier to deploy
17+
* Running applications in environments where just-in-time compilation isn't supported
18+
19+
EF NativeAOT applications start up much faster than the same applications without NativeAOT; aside from the general startup improvements that NativeAOT offers (i.e. no JIT compilation required on each startup), EF's NativeAOT supports removes the processing of LINQ queries and their translation to SQL; the more EF LINQ queries an application has in its code, the faster the startup gains are expected to be from NativeAOT.
20+
21+
## Publishing an EF NativeAOT Application
22+
23+
First, enable NativeAOT publishing for your project as follows:
24+
25+
```xml
26+
<PropertyGroup>
27+
<PublishAot>true</PublishAot>
28+
</PropertyGroup>
29+
```
30+
31+
EF's support for LINQ query execution under NativeAOT relies on *query precompilation*: this mechanism statically identifies EF LINQ queries and generates C# [*interceptors*](/dotnet/csharp/whats-new/csharp-12#interceptors), which contain code to execute each specific query. This can significantly cut down on your application's startup time, as the heavy lifting of processing and compiling your LINQ queries into SQL no longer happens every time your application starts up. Instead, each query's interceptor contains the finalized SQL for that query, as well as optimized code to materialize database results as .NET objects.
32+
33+
C# interceptors are currently an experimental feature, and require a special opt-in in your project file:
34+
35+
```xml
36+
<PropertyGroup>
37+
<InterceptorsNamespaces>$(InterceptorsPreviewNamespaces);Microsoft.EntityFrameworkCore.GeneratedInterceptors</InterceptorsNamespaces>
38+
</PropertyGroup>
39+
```
40+
41+
At this point, you're ready to precompile your LINQ queries, and generate the [compiled model](xref:core/performance/advanced-performance-topics#compiled-models) that they depend on. Make sure that you have at least version 9.0 of the EF tools (`dotnet tool list -g`), and then execute the following:
42+
43+
```console
44+
dotnet ef dbcontext optimize --precompile-queries --nativeaot
45+
```
46+
47+
You're now ready to publish your EF NativeAOT application:
48+
49+
```console
50+
dotnet publish -r linux-arm64 -c Release
51+
```
52+
53+
This shows publishing a NativeAOT publishing for Linux running on ARM64; [consult this catalog](/dotnet/core/rid-catalog) to find your runtime identifier.
54+
55+
Due to the way C# interceptors work, any change in the application source invalidates them and requires repeating the above process. As a result, interceptor generation and actual publishing aren't expected to happen in the inner loop, as the developer is working on code; instead, both `dotnet ef dbcontext optimize` and `dotnet publish` can be executed in a publishing/deployment workflow, in a CI/CD system.
56+
57+
> [!NOTE]
58+
> Publishing currently reports a number of trimming and NativeAOT warnings, meaning that your application isn't fully guaranteed to run properly. This is expected given the current experimental state of NativeAOT support; the final, non-experimental feature will report no warnings.
59+
60+
## Limitations
61+
62+
### Dynamic queries are not supported
63+
64+
Query precompilation performs static analysis of your source code, identifying EF LINQ queries and generating C# interceptors for them. LINQ allows expressing highly dynamic queries, where LINQ operators are composed based on arbitrary conditions; such queries unfortunately cannot be statically analyzed, and are currently unsupported. Consider the following example:
65+
66+
```c#
67+
IAsyncEnumerable<Blog> GetBlogs(BlogContext context, bool applyFilter)
68+
{
69+
IQueryable<Blog> query = context.Blogs.OrderBy(b => b.Id);
70+
71+
if (applyFilter)
72+
{
73+
query = query.Where(b => b.Name != "foo");
74+
}
75+
76+
return query.AsAsyncEnumerable();
77+
}
78+
```
79+
80+
The above query is split across several statements, and dynamically composes the `Where` operator based on an external parameter; such queries cannot be precompiled. However, it is sometimes possible to rewrite such dynamic queries as multiple non-dynamic queries:
81+
82+
```c#
83+
IAsyncEnumerable<Blog> GetBlogs(BlogContext context, bool applyFilter)
84+
=> applyFilter
85+
? context.Blogs.OrderBy(b => b.Id).Where(b => b.Name != "foo").AsAsyncEnumerable()
86+
: context.Blogs.OrderBy(b => b.Id).AsAsyncEnumerable();
87+
```
88+
89+
Since the two queries can each be statically analyzed from start to finish, precompilation can handle them.
90+
91+
Note that dynamic queries will likely be supported in the future when using NativeAOT; however, since they cannot be precompiled, they will continue to slow down your application startup, and will also generally perform less efficiently compared to non-NativeAOT execution; this is because EF internally relies on code generation to materialize database results, but code generation is not supported when using NativeAOT.
92+
93+
### Other limitations
94+
95+
* LINQ query expression syntax (sometimes termed "comprehension syntax") is not supported.
96+
* The generated compiled model and query interceptors may currently be quite large in terms of code size, and take a long while to generate. We plan on improving this.
97+
* EF providers may need to build in support for precompiled queries; check your provider's documentation to know whether it is compatible with EF's NativeAOT support.
98+
* Value converters that use captured state are not supported.
99+
100+
## Precompiled queries without NativeAOT
101+
102+
Because of the current limitations of EF's NativeAOT support, it may not be usable for some applications. However, you may be able to take advantage of precompiled queries while publishing regular, non-NativeAOT applications; this allows you to at least benefit from the startup time reduction that precompiled queries offer, while being able to use dynamic queries and other features not currently supported with NativeAOT.
103+
104+
Using precompiled queries without NativeAOT is simply a matter of executing the following:
105+
106+
```console
107+
dotnet ef dbcontext optimize --precompile-queries
108+
```
109+
110+
As shown above, this will generate a compiled model and interceptors for queries which could be precompiled, removing their overhead from your application's startup time.

entity-framework/core/what-is-new/ef-core-9.0/whatsnew.md

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -296,21 +296,36 @@ Note, however, that we plan to fully remove sync support in EF 11, so start upda
296296

297297
## AOT and pre-compiled queries
298298

299-
As mentioned in the introduction, there is a lot of work going on behind the scenes to allow EF Core to run without just-in-time (JIT) compilation. Instead, EF compile ahead-of-time (AOT) everything needed to run queries in the application. This AOT compilation and related processing will happen as part of building and publishing the application. At this point in the EF9 release, there is not much available that can be used by you, the app developer. However, for those interested, the completed issues in EF9 that support AOT and pre-compiled queries are:
300-
301-
* [Compiled model: Use static binding instead of reflection for properties and fields](https://github.com/dotnet/efcore/issues/24900)
302-
* [Compiled model: Generate lambdas used in change tracking](https://github.com/dotnet/efcore/issues/24904)
303-
* [Make change tracking and the update pipeline compatible with AOT/trimming](https://github.com/dotnet/efcore/issues/29761)
304-
* [Use interceptors to redirect the query to precompiled code](https://github.com/dotnet/efcore/issues/31331)
305-
* [Make all SQL expression nodes quotable](https://github.com/dotnet/efcore/issues/33008)
306-
* [Generate the compiled model during build](https://github.com/dotnet/efcore/issues/24894)
307-
* [Discover the compiled model automatically](https://github.com/dotnet/efcore/issues/24893)
308-
* [Make ParameterExtractingExpressionVisitor capable of extracting paths to evaluatable fragments in the tree](https://github.com/dotnet/efcore/issues/32999)
309-
* [Generate expression trees in compiled models (query filters, value converters)](https://github.com/dotnet/efcore/issues/29924)
310-
* [Make LinqToCSharpSyntaxTranslator more resilient to multiple declaration of the same variable in nested scopes](https://github.com/dotnet/efcore/issues/32716)
311-
* [Optimize ParameterExtractingExpressionVisitor](https://github.com/dotnet/efcore/issues/32698)
312-
313-
Check back here for examples of how to use pre-compiled queries as the experience comes together.
299+
> [!WARNING]
300+
> NativeAOT and query precompilation are highly experimental feature, and are not yet suited for production use. The support described below should be viewed as infrastructure towards the final feature, which will likely be released with EF 10. We encourage you to experiment with the current support and report on your experiences, but recommend against deploying EF NativeAOT applications in production.
301+
302+
EF 9.0 brings initial, experimental support for [.NET NativeAOT](/dotnet/core/deploying/native-aot), allowing the publishing of ahead-of-time compiled applications which make use of EF to access databases. To support LINQ queries in NativeAOT mode, EF reiles on _query precompilation_: this mechanism statically identifies EF LINQ queries and generates C# [_interceptors_](/dotnet/csharp/whats-new/csharp-12#interceptors), which contain code to execute each specific query. This can significantly cut down on your application's startup time, as the heavy lifting of processing and compiling your LINQ queries into SQL no longer happens every time your application starts up. Instead, each query's interceptor contains the finalized SQL for that query, as well as optimized code to materialize database results as .NET objects.
303+
304+
For example, given a program with the following EF query:
305+
306+
```c#
307+
var blogs = await context.Blogs.Where(b => b.Name == "foo").ToListAsync();
308+
```
309+
310+
EF will generate a C# interceptor into your project, which will take over the query execution. Instead of processing the query and translating it to SQL every time the program starts, the interceptor has the SQL embedded right into it, allowing your program to start up much faster:
311+
312+
```c#
313+
var relationalCommandTemplate = ((IRelationalCommandTemplate)(new RelationalCommand(materializerLiftableConstantContext.CommandBuilderDependencies, "SELECT [b].[Id], [b].[Name]\nFROM [Blogs] AS [b]\nWHERE [b].[Name] = N'foo'", new IRelationalParameter[] { })));
314+
```
315+
316+
In addition, the same interceptor contains code to materialize your .NET object from database results:
317+
318+
```c#
319+
var instance = new Blog();
320+
UnsafeAccessor_Blog_Id_Set(instance) = dataReader.GetInt32(0);
321+
UnsafeAccessor_Blog_Name_Set(instance) = dataReader.GetString(1);
322+
```
323+
324+
This uses another new .NET feature - [unsafe accessors](/dotnet/api/system.runtime.compilerservices.unsafeaccessorattribute), to inject data from the database into your object's private fields.
325+
326+
If you're interested in NativeAOT and like to experiment with cutting-edge features, give this a try! Just be aware that the feature should be consider unstable, and currently has many limitations; we expect to stabilize it and make it more suitable for production usage in EF 10.
327+
328+
See the [NativeAOT documentation page](xref:core/miscellaneous/nativeaot-and-precompiled-queries) for more details.
314329

315330
## LINQ and SQL translation
316331

entity-framework/toc.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,11 +361,12 @@
361361
href: core/miscellaneous/async.md
362362
- name: Nullable reference types
363363
href: core/miscellaneous/nullable-reference-types.md
364-
#- name: Using dependency injection
365364
- name: Collations and case sensitivity
366365
href: core/miscellaneous/collations-and-case-sensitivity.md
367366
- name: Connection resiliency
368367
href: core/miscellaneous/connection-resiliency.md
368+
- name: NativeAOT and precompiled queries
369+
href: core/miscellaneous/nativeaot-and-precompiled-queries.md
369370
- name: Connection strings
370371
href: core/miscellaneous/connection-strings.md
371372
- name: Context pooling

0 commit comments

Comments
 (0)