Suporte NativeAOT e consultas pré-compiladas (experimental)
Aviso
NativeAOT e pré-compilação de consulta são recursos altamente experimentais e ainda não são adequados para uso em produção. O suporte descrito abaixo deve ser visto como infraestrutura para o recurso final, que provavelmente será lançado com o EF 10. Incentivamos você a experimentar o suporte atual e relatar suas experiências, mas não recomendamos a implantação de aplicativos EF NativeAOT em produção. Veja abaixo as limitações conhecidas específicas.
O .NET NativeAOT permite a publicação de aplicativos .NET independentes que foram compilados antecipadamente (AOT). Isso oferece as seguintes vantagens:
- Tempo de inicialização do aplicativo significativamente mais rápido
- Binários pequenos e independentes que ocupam menos espaço de memória e são mais fáceis de implantar
- Executando aplicativos em ambientes em que não há suporte para compilação just-in-time
Os aplicativos EF publicados com o NativeAOT são iniciados muito mais rapidamente do que os mesmos aplicativos sem ele. Além das melhorias gerais de inicialização do .NET que o NativeAOT oferece (ou seja, nenhuma compilação JIT necessária a cada vez), o EF também pré-compila consultas LINQ ao publicar seu aplicativo, para que nenhum processamento seja necessário ao inicializar e o SQL já esteja disponível para execução imediata. Quanto mais consultas EF LINQ um aplicativo tiver em seu código, mais rápidos serão os ganhos de inicialização.
Publicando um aplicativo EF NativeAOT
Primeiro, habilite a publicação NativeAOT para seu projeto da seguinte maneira:
<PropertyGroup>
<PublishAot>true</PublishAot>
</PropertyGroup>
O suporte do EF para execução de consulta LINQ em NativeAOT depende da pré-compilação de consulta: esse mecanismo identifica estaticamente consultas EF LINQ e gera interceptores C#, que contêm código para executar cada consulta específica. Isso pode reduzir significativamente o tempo de inicialização do aplicativo, pois o trabalho pesado de processamento e compilação de suas consultas LINQ em SQL não acontece mais toda vez que o aplicativo é iniciado. Em vez disso, o interceptor de cada consulta contém o SQL finalizado para essa consulta, bem como o código otimizado para materializar os resultados do banco de dados como objetos .NET.
Os interceptores C# são atualmente um recurso experimental e exigem uma aceitação especial em seu arquivo de projeto:
<PropertyGroup>
<InterceptorsNamespaces>$(InterceptorsPreviewNamespaces);Microsoft.EntityFrameworkCore.GeneratedInterceptors</InterceptorsNamespaces>
</PropertyGroup>
Por fim, o pacote contém a Microsoft.EntityFrameworkCore.Tasks
integração do MSBuild que executará a pré-compilação de consulta (e gerará o modelo compilado necessário) quando você publicar seu aplicativo:
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Tasks" Version="9.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
Agora você está pronto para publicar seu aplicativo EF NativeAOT:
dotnet publish -r linux-arm64 -c Release
Isso mostra a publicação de uma publicação NativeAOT para Linux em execução no ARM64; Consulte este catálogo para encontrar seu identificador de tempo de execução. Se você quiser gerar os interceptores sem publicar - por exemplo, para examinar as fontes geradas - você pode fazê-lo por meio do dotnet ef dbcontext optimize --precompile-queries --nativeaot
comando.
Devido à maneira como os interceptores C# funcionam, qualquer alteração na origem do aplicativo os invalida e requer a repetição do processo acima. Como resultado, não se espera que a geração de interceptores e a publicação real aconteçam no loop interno, pois o desenvolvedor está trabalhando no código; em vez disso, ambos dotnet ef dbcontext optimize
podem dotnet publish
ser executados em um fluxo de trabalho de publicação/implantação, em um sistema de CI/CD.
Observação
Atualmente, a publicação relata vários avisos de corte e NativeAOT, o que significa que seu aplicativo não tem garantia total de execução correta. Isso é esperado dado o estado experimental atual do suporte ao NativeAOT; O recurso final, não experimental, não relatará nenhum aviso.
Limitações
Não há suporte para consultas dinâmicas
A pré-compilação de consulta executa a análise estática do código-fonte, identificando consultas EF LINQ e gerando interceptores C# para elas. O LINQ permite expressar consultas altamente dinâmicas, em que os operadores LINQ são compostos com base em condições arbitrárias; Infelizmente, essas consultas não podem ser analisadas estaticamente e não têm suporte no momento. Considere o seguinte exemplo:
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();
}
A consulta acima é dividida em várias instruções e compõe dinamicamente o Where
operador com base em um parâmetro externo; essas consultas não podem ser pré-compiladas. No entanto, às vezes é possível reescrever essas consultas dinâmicas como várias consultas não dinâmicas:
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();
Como as duas consultas podem ser analisadas estaticamente do início ao fim, a pré-compilação pode lidar com elas.
Observe que as consultas dinâmicas provavelmente terão suporte no futuro ao usar o NativeAOT; no entanto, como eles não podem ser pré-compilados, eles continuarão a retardar a inicialização do aplicativo e também geralmente terão um desempenho menos eficiente em comparação com a execução não NativeAOT; isso ocorre porque o EF depende internamente da geração de código para materializar os resultados do banco de dados, mas não há suporte para a geração de código ao usar NativeAOT.
Outras limitações
- Não há suporte para a sintaxe de expressão de consulta LINQ (às vezes chamada de "sintaxe de compreensão").
- O modelo compilado gerado e os interceptores de consulta podem ser bastante grandes em termos de tamanho de código e demorar muito para serem gerados. Planejamos melhorar isso.
- Os provedores de EF podem precisar criar suporte para consultas pré-compiladas; verifique a documentação do seu provedor para saber se ele é compatível com o suporte NativeAOT da EF.
- Não há suporte para conversores de valor que usam o estado capturado.
Consultas pré-compiladas sem NativeAOT
Devido às limitações atuais do suporte NativeAOT do EF, ele pode não ser utilizável para alguns aplicativos. No entanto, você pode aproveitar as consultas pré-compiladas ao publicar aplicativos regulares não NativeAOT; isso permite que você pelo menos se beneficie da redução do tempo de inicialização que as consultas pré-compiladas oferecem, enquanto pode usar consultas dinâmicas e outros recursos não suportados atualmente com o NativeAOT.
Usar consultas pré-compiladas sem NativeAOT é simplesmente uma questão de executar o seguinte:
dotnet ef dbcontext optimize --precompile-queries
Como mostrado acima, isso gerará um modelo compilado e interceptores para consultas que podem ser pré-compiladas, removendo sua sobrecarga do tempo de inicialização do seu aplicativo.