NativeAOT 支持和预编译查询 (实验性)
警告
NativeAOT 和查询预编译是高度实验性的功能,尚不适合生产用途。 下面所述的支持应被视为基础结构,以便使用 EF 10 发布最终功能。 我们鼓励你尝试当前的支持并报告你的体验,但建议在生产环境中部署 EF NativeAOT 应用程序。 有关特定已知限制,请参阅下文。
.NET NativeAOT 允许发布预先编译的自包含 .NET 应用程序(AOT)。 这样做具有以下优势:
- 应用程序启动时间明显加快
- 具有较小内存占用且更易于部署的小型自包含二进制文件
- 在不支持实时编译的环境中运行应用程序
使用 NativeAOT 发布的 EF 应用程序比没有本机应用程序的启动速度要快得多。 除了 NativeAOT 提供的常规 .NET 启动改进(即每次不需要 JIT 编译),EF 还会在发布应用程序时预编译 LINQ 查询,以便在启动时不需要处理,并且 SQL 已可用于立即执行。 应用程序在其代码中查询 EF LINQ 越多,启动提升的速度就越快。
发布 EF NativeAOT 应用程序
首先,为项目启用 NativeAOT 发布,如下所示:
<PropertyGroup>
<PublishAot>true</PublishAot>
</PropertyGroup>
EF 对 NativeAOT 下的 LINQ 查询执行的支持依赖于 查询预编译:此机制静态标识 EF LINQ 查询并生成 C# 侦听器,其中包含执行每个特定查询的代码。 这可以显著减少应用程序的启动时间,因为每次启动应用程序时,处理和编译 LINQ 查询的繁重都不再发生。 相反,每个查询的拦截器都包含该查询的最终 SQL,以及优化代码,以将数据库结果具体化为 .NET 对象。
C# 拦截器目前是一项实验性功能,需要在项目文件中进行特殊的选择加入:
<PropertyGroup>
<InterceptorsNamespaces>$(InterceptorsPreviewNamespaces);Microsoft.EntityFrameworkCore.GeneratedInterceptors</InterceptorsNamespaces>
</PropertyGroup>
最后,包 Microsoft.EntityFrameworkCore.Tasks
包含 MSBuild 集成 ,将在发布应用程序时执行查询预编译(并生成所需的编译模型):
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Tasks" Version="9.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
现已准备好发布 EF NativeAOT 应用程序:
dotnet publish -r linux-arm64 -c Release
这显示了发布在 ARM64 上运行的 Linux 的 NativeAOT 发布; 请查阅此目录 以查找运行时标识符。 如果要在不发布的情况下生成拦截器(例如检查生成的源)可以通过命令执行此操作 dotnet ef dbcontext optimize --precompile-queries --nativeaot
。
由于 C# 拦截器的工作方式,应用程序源中的任何更改都使它们失效,并且需要重复上述过程。 因此,侦听器生成和实际发布不应在内部循环中发生,因为开发人员正在处理代码;而是可以在 CI/CD 系统中的发布/部署工作流中执行。dotnet ef dbcontext optimize
dotnet publish
注意
发布当前会报告大量剪裁和 NativeAOT 警告,这意味着应用程序无法完全保证正常运行。 这应为 NativeAOT 支持的当前实验状态;最终的非实验性功能不会报告任何警告。
限制
不支持动态查询
查询预编译对源代码执行静态分析,识别 EF LINQ 查询并为其生成 C# 拦截器。 LINQ 允许表达高度动态的查询,其中 LINQ 运算符基于任意条件进行组合;遗憾的是,无法静态分析此类查询,并且当前不受支持。 请考虑以下示例:
IAsyncEnumerable<Blog> GetBlogs(BlogContext context, bool applyFilter)
{
IQueryable<Blog> query = context.Blogs.OrderBy(b => b.Id);
if (applyFilter)
{
query = query.Where(b => b.Name != "foo");
}
return query.AsAsyncEnumerable();
}
上述查询拆分为多个语句,并基于外部参数动态组合 Where
运算符;无法预编译此类查询。 但是,有时可以将此类动态查询重写为多个非动态查询:
IAsyncEnumerable<Blog> GetBlogs(BlogContext context, bool applyFilter)
=> applyFilter
? context.Blogs.OrderBy(b => b.Id).Where(b => b.Name != "foo").AsAsyncEnumerable()
: context.Blogs.OrderBy(b => b.Id).AsAsyncEnumerable();
由于两个查询都可以从头到尾静态分析,因此预编译可以处理它们。
请注意,使用 NativeAOT 时,将来可能会支持动态查询;但是,由于它们无法预编译,因此它们将继续减慢应用程序启动速度,并且通常与非 NativeAOT 执行相比,性能也会降低;这是因为 EF 在内部依赖于代码生成来具体化数据库结果,但在使用 NativeAOT 时不支持生成代码。
其他限制
- 不支持 LINQ 查询表达式语法(有时称为“理解语法”。
- 生成的已编译模型和查询拦截器目前在代码大小方面可能相当大,并且需要很长时间才能生成。 我们计划改进这一点。
- EF 提供程序可能需要生成支持预编译查询;检查提供程序的文档,了解它是否与 EF 的 NativeAOT 支持兼容。
- 不支持使用捕获状态的值转换器。
没有 NativeAOT 的预编译查询
由于 EF 的 NativeAOT 支持目前存在限制,因此某些应用程序可能不可用。 但是,在发布常规的非 NativeAOT 应用程序时,你可能能够利用预编译的查询;这允许你至少受益于预编译查询提供的启动时间减少,同时能够使用 NativeAOT 当前不支持的动态查询和其他功能。
使用没有 NativeAOT 的预编译查询只是执行以下事项:
dotnet ef dbcontext optimize --precompile-queries
如上所示,这将为可以预编译的查询生成编译的模型和拦截器,从而从应用程序的启动时间中删除其开销。