Prise en charge nativeAOT et requêtes précompilées (expérimentales)
Avertissement
La précompilation nativeAOT et de requête est une fonctionnalité hautement expérimentale et n’est pas encore adaptée à l’utilisation de production. La prise en charge décrite ci-dessous doit être considérée comme une infrastructure vers la fonctionnalité finale, qui sera probablement publiée avec EF 10. Nous vous encourageons à expérimenter le support et le rapport actuels sur vos expériences, mais nous vous recommandons de déployer des applications EF NativeAOT en production. Consultez ci-dessous pour connaître les limitations connues spécifiques.
.NET NativeAOT permet de publier des applications .NET autonomes qui ont été compilées à l’avance (AOT). Cela offre les avantages suivants :
- Temps de démarrage de l’application beaucoup plus rapide
- Petits fichiers binaires autonomes qui ont des empreintes mémoire plus petites et sont plus faciles à déployer
- Exécution d’applications dans des environnements où la compilation juste-à-temps n’est pas prise en charge
Les applications EF publiées avec NativeAOT démarrent beaucoup plus rapidement que les mêmes applications sans celle-ci. Outre les améliorations générales apportées au démarrage de .NET que NativeAOT offre (c’est-à-dire qu’aucune compilation JIT n’est requise à chaque fois), EF précompile également les requêtes LINQ lors de la publication de votre application, de sorte qu’aucun traitement n’est nécessaire lors du démarrage et que sql est déjà disponible pour l’exécution immédiate. Plus EF LINQ interroge une application dans son code, plus les gains de démarrage sont attendus.
Publication d’une application EF NativeAOT
Tout d’abord, activez la publication NativeAOT pour votre projet comme suit :
<PropertyGroup>
<PublishAot>true</PublishAot>
</PropertyGroup>
La prise en charge d’EF pour l’exécution des requêtes LINQ sous NativeAOT repose sur la précompilation des requêtes : ce mécanisme identifie statiquement les requêtes EF LINQ et génère des intercepteurs C#, qui contiennent du code pour exécuter chaque requête spécifique. Cela peut réduire considérablement le temps de démarrage de votre application, car le traitement et la compilation de vos requêtes LINQ dans SQL ne se produisent plus chaque fois que votre application démarre. Au lieu de cela, l’intercepteur de chaque requête contient le SQL finalisé pour cette requête, ainsi que le code optimisé pour matérialiser les résultats de la base de données en tant qu’objets .NET.
Les intercepteurs C# sont actuellement une fonctionnalité expérimentale et nécessitent un opt-in spécial dans votre fichier projet :
<PropertyGroup>
<InterceptorsNamespaces>$(InterceptorsPreviewNamespaces);Microsoft.EntityFrameworkCore.GeneratedInterceptors</InterceptorsNamespaces>
</PropertyGroup>
Enfin, le Microsoft.EntityFrameworkCore.Tasks
package contient l’intégration MSBuild qui effectue la précompilation de requête (et génère le modèle compilé requis) lorsque vous publiez votre application :
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Tasks" Version="9.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
Vous êtes maintenant prêt à publier votre application EF NativeAOT :
dotnet publish -r linux-arm64 -c Release
Cela montre la publication d’une publication NativeAOT pour Linux s’exécutant sur ARM64 ; consultez ce catalogue pour trouver votre identificateur d’exécution. Si vous souhaitez générer les intercepteurs sans publication , par exemple pour examiner les sources générées, vous pouvez le faire via la dotnet ef dbcontext optimize --precompile-queries --nativeaot
commande.
En raison du fonctionnement des intercepteurs C#, toute modification de la source de l’application les invalide et nécessite la répétition du processus ci-dessus. Par conséquent, la génération d’intercepteur et la publication réelle ne sont pas censées se produire dans la boucle interne, car le développeur travaille sur du code ; Au lieu de cela, les deux dotnet ef dbcontext optimize
et dotnet publish
peuvent être exécutés dans un flux de travail de publication/déploiement, dans un système CI/CD.
Remarque
La publication signale actuellement un certain nombre d’avertissements de découpage et nativeAOT, ce qui signifie que votre application n’est pas entièrement garantie de s’exécuter correctement. Ceci est attendu compte tenu de l’état expérimental actuel de la prise en charge nativeAOT ; la fonctionnalité finale non expérimentale ne signale aucun avertissement.
Limites
Les requêtes dynamiques ne sont pas prises en charge
La précompilation des requêtes effectue une analyse statique de votre code source, en identifiant les requêtes EF LINQ et en générant des intercepteurs C# pour eux. LINQ permet d’exprimer des requêtes hautement dynamiques, où les opérateurs LINQ sont composés en fonction de conditions arbitraires ; Ces requêtes ne peuvent malheureusement pas être analysées statiquement et ne sont actuellement pas prises en charge. Prenons l’exemple suivant :
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();
}
La requête ci-dessus est divisée en plusieurs instructions et compose dynamiquement l’opérateur Where
en fonction d’un paramètre externe ; ces requêtes ne peuvent pas être précompilées. Toutefois, il est parfois possible de réécrire des requêtes dynamiques telles que plusieurs requêtes non dynamiques :
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();
Étant donné que les deux requêtes peuvent chacune être analysées de manière statique du début à la fin, la précompilation peut les gérer.
Notez que les requêtes dynamiques seront probablement prises en charge ultérieurement lors de l’utilisation de NativeAOT ; toutefois, étant donné qu’ils ne peuvent pas être précompilés, ils continueront à ralentir le démarrage de votre application et seront également généralement moins efficaces par rapport à l’exécution non nativeAOT ; Cela est dû au fait que EF s’appuie en interne sur la génération de code pour matérialiser les résultats de la base de données, mais la génération de code n’est pas prise en charge lors de l’utilisation de NativeAOT.
Autres limitations
- La syntaxe d’expression de requête LINQ (parfois appelée « syntaxe de compréhension ») n’est pas prise en charge.
- Le modèle compilé et les intercepteurs de requête générés peuvent actuellement être assez volumineux en termes de taille de code et prendre un certain temps pour générer. Nous prévoyons d’améliorer cela.
- Les fournisseurs EF peuvent avoir besoin de générer une prise en charge des requêtes précompilées ; consultez la documentation de votre fournisseur pour savoir s’il est compatible avec la prise en charge NativeAOT d’EF.
- Les convertisseurs de valeurs qui utilisent l’état capturé ne sont pas pris en charge.
Requêtes précompilées sans NativeAOT
En raison des limitations actuelles de la prise en charge NativeAOT d’EF, il peut ne pas être utilisable pour certaines applications. Toutefois, vous pouvez tirer parti des requêtes précompilées lors de la publication d’applications standard et non NativeAOT ; Cela vous permet au moins d’tirer parti de la réduction du temps de démarrage de l’offre de requêtes précompilées, tout en étant en mesure d’utiliser des requêtes dynamiques et d’autres fonctionnalités non prises en charge actuellement avec NativeAOT.
L’utilisation de requêtes précompilées sans NativeAOT est simplement une question d’exécution des éléments suivants :
dotnet ef dbcontext optimize --precompile-queries
Comme indiqué ci-dessus, cela génère un modèle compilé et des intercepteurs pour les requêtes qui peuvent être précompilées, en supprimant leur surcharge du temps de démarrage de votre application.