Partilhar via


Suporte NativeAOT e Consultas Pré-Compiladas (Experimental)

Advertência

NativeAOT e pré-compilação de queries são recursos altamente experimentais e ainda não estão adequados para uso em produção. O suporte descrito abaixo deve ser visto como infraestrutura para o recurso final, que será lançado em uma versão futura. Recomendamos que você experimente o suporte atual e relate suas experiências, mas recomendamos que não implante aplicativos EF NativeAOT na produção. Veja abaixo as limitações específicas conhecidas.

O .NET NativeAOT permite a publicação de aplicativos .NET independentes que foram compilados antecipadamente (AOT). Fazê-lo oferece as seguintes vantagens:

  • Tempo de inicialização do aplicativo significativamente mais rápido
  • Binários pequenos e autônomos que têm menor espaço de memória e são mais fáceis de implantar
  • Executando aplicativos em ambientes onde a compilação just-in-time não é suportada

Os aplicativos EF publicados com o NativeAOT são iniciados muito mais rápido 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, de modo que nenhum processamento seja necessário ao iniciar 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ápido 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 a execução de consultas LINQ em NativeAOT depende da pré-compilação de consultas: esse mecanismo identifica estaticamente consultas EF LINQ e gera intercetadores C#, que contêm código para executar cada consulta específica. Isso pode reduzir significativamente o tempo de inicialização do seu aplicativo, já que o trabalho pesado de processamento e compilação de suas consultas LINQ em SQL não acontece mais toda vez que seu aplicativo é iniciado. Em vez disso, o intercetador de cada consulta contém o SQL finalizado para essa consulta, bem como código otimizado para materializar os resultados do banco de dados como objetos .NET.

Os intercetadores C# são atualmente um recurso experimental e exigem uma opção especial no arquivo de projeto:

<PropertyGroup>
  <InterceptorsNamespaces>$(InterceptorsNamespaces);Microsoft.EntityFrameworkCore.GeneratedInterceptors</InterceptorsNamespaces>
</PropertyGroup>

Finalmente, o Microsoft.EntityFrameworkCore.Tasks pacote contém integração MSBuild que realizará a pré-compilação das consultas (e gerará o modelo compilado necessário) quando publicar a sua aplicação:

<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

Observação

O comando publish pode falhar com erro CS9137. A causa desse erro são referências de pacotes transitivos desatualizadas Microsoft.CodeAnalysis.CSharp.Workspaces e Microsoft.CodeAnalysis.Workspaces.MSBuild. Como solução alternativa, adicione esses pacotes explicitamente ao seu arquivo .csproj:

<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.13.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="4.13.0" />

Consulte esta edição para obter mais informações.

Isso mostra a publicação de uma publicação NativeAOT para Linux rodando em ARM64; Consulte este catálogo para encontrar seu identificador de tempo de execução. Se você quiser gerar os intercetadores sem publicar - por exemplo, para examinar as fontes geradas - você pode fazê-lo através do dotnet ef dbcontext optimize --precompile-queries --nativeaot comando.

Devido à forma como os intercetadores C# funcionam, qualquer alteração na fonte do aplicativo os invalida e requer a repetição do processo acima. Como resultado, não se espera que a geração de intercetores e a publicação real aconteçam no loop interno, pois o desenvolvedor está trabalhando em código; em vez disso, ambos dotnet ef dbcontext optimize e dotnet publish podem ser executados em um fluxo de trabalho de publicação/implantação, em um sistema de CI/CD.

Observação

Atualmente, a publicação reporta uma série de avisos de corte e NativeAOT, o que significa que o seu aplicativo não tem total garantia de ser executado corretamente. Isto é esperado dado o estado experimental atual do suporte NativeAOT; A versão final, não experimental, não apresentará quaisquer avisos.

Limitações

Não há suporte para consultas dinâmicas

A pré-compilação de consultas executa a análise estática do código-fonte, identificando consultas EF LINQ e gerando intercetadores C# para elas. O LINQ permite expressar consultas altamente dinâmicas, onde os operadores LINQ são compostos com base em condições arbitrárias; Infelizmente, essas consultas não podem ser analisadas estaticamente e atualmente não são suportadas. 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, tais 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 consultas dinâmicas provavelmente serão suportadas 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 a geração de código não é suportada ao usar o NativeAOT.

Outras limitações

  • A sintaxe da expressão de consulta LINQ (às vezes denominada "sintaxe de compreensão") não é suportada.
  • O modelo compilado gerado e os intercetadores de consulta podem atualmente ser bastante grandes em termos de tamanho de código e levar muito tempo para serem gerados. Pretendemos melhorar esta situação.
  • Os provedores de EF podem precisar criar suporte para consultas pré-compiladas; verifique a documentação do seu provedor para saber se ela é 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 nativos AOT; isso permite que você pelo menos se beneficie da redução de 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 intercetadores para consultas que podem ser pré-compiladas, removendo sua sobrecarga do tempo de inicialização do seu aplicativo.