Obsługa funkcji NativeAOT i wstępnie skompilowane zapytania (eksperymentalne)

Ostrzeżenie

Prekompilacja NativeAOT i zapytań jest wysoce eksperymentalna i nie jest jeszcze odpowiednia do użytku produkcyjnego. Pomoc techniczna opisana poniżej powinna być postrzegana jako infrastruktura w kierunku ostatniej funkcji, która zostanie wydana w przyszłej wersji. Zachęcamy do eksperymentowania z bieżącym wsparciem i raportowania o swoich doświadczeniach, ale odradzamy wdrażanie aplikacji EF NativeAOT w środowisku produkcyjnym. Poniżej przedstawiono konkretne znane ograniczenia.

Platforma .NET NativeAOT umożliwia publikowanie samodzielnych aplikacji .NET, które zostały skompilowane przed czasem (AOT). Zapewnia to następujące korzyści:

  • Znacznie krótszy czas uruchamiania aplikacji
  • Małe, samodzielne pliki binarne, które mają mniejsze zużycie pamięci i są łatwiejsze do wdrożenia
  • Uruchamianie aplikacji w środowiskach, w których kompilacja just in time nie jest obsługiwana

Aplikacje EF opublikowane za pomocą funkcji NativeAOT są uruchamiane znacznie szybciej niż te same aplikacje bez niej. Oprócz ogólnych ulepszeń uruchamiania platformy .NET, które oferuje nativeAOT (tj. nie jest wymagana kompilacja JIT za każdym razem), program EF prekompiluje również zapytania LINQ podczas publikowania aplikacji, aby nie było wymagane żadne przetwarzanie podczas uruchamiania, a program SQL jest już dostępny do natychmiastowego wykonania. Im więcej zapytań EF LINQ aplikacja ma w swoim kodzie, tym szybsze przyspieszenie uruchamiania się oczekuje.

Publikowanie aplikacji EF NativeAOT

Najpierw włącz publikowanie nativeAOT dla projektu w następujący sposób:

<PropertyGroup>
    <PublishAot>true</PublishAot>
</PropertyGroup>

Obsługa wykonywania zapytań LINQ w ramach funkcji NativeAOT jest oparta na prekompilacji zapytań: ten mechanizm statycznie identyfikuje zapytania EF LINQ i generuje przechwytniki języka C#, które zawierają kod do wykonania poszczególnych zapytań. Może to znacząco zmniejszyć czas uruchamiania aplikacji, ponieważ duże obciążenie przetwarzania i kompilowania zapytań LINQ w języku SQL nie odbywa się już za każdym razem, gdy aplikacja się uruchamia. Zamiast tego interceptory każdego zapytania zawierają sfinalizowane zapytanie SQL dla tego zapytania, a także zoptymalizowany kod do materializowania wyników bazy danych jako obiekty platformy .NET.

Interceptory C# są obecnie funkcją eksperymentalną i wymagają specjalnej aktywacji w pliku projektu.

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

Na koniec pakiet zawiera Microsoft.EntityFrameworkCore.Tasks, która przeprowadzi wstępne kompilowanie zapytania (i wygeneruje wymagany skompilowany model) podczas publikowania aplikacji:

<ItemGroup>
  <PackageReference Include="Microsoft.EntityFrameworkCore.Tasks" Version="9.0.0">
    <PrivateAssets>all</PrivateAssets>
    <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
  </PackageReference>
</ItemGroup>

Teraz możesz opublikować aplikację EF NativeAOT:

dotnet publish -r linux-arm64 -c Release

Uwaga

Polecenie publikowania może zakończyć się niepowodzeniem z powodu błędu CS9137. Przyczyną tego błędu są nieaktualne odwołania pakietów przechodnich Microsoft.CodeAnalysis.CSharp.Workspaces i Microsoft.CodeAnalysis.Workspaces.MSBuild. Aby obejść ten problem, dodaj te pakiety jawnie do pliku csproj:

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

Zobacz tę kwestię w celu uzyskania więcej informacji.

W ten sposób można opublikować NativeAOT dla systemu Linux działającego na ARM64; zapoznaj się z tym katalogiem, aby znaleźć identyfikator runtime'u. Jeśli chcesz wygenerować intereptory bez publikowania — na przykład w celu zbadania wygenerowanych źródeł — możesz to zrobić za pomocą polecenia dotnet ef dbcontext optimize --precompile-queries --nativeaot.

Ze względu na sposób działania interceptorów w języku C#, wszelkie zmiany w kodzie źródłowym aplikacji unieważniają je i wymagają powtórzenia powyższego procesu. W związku z tym generowanie przechwytujących i rzeczywiste publikowanie nie powinno mieć miejsca w wewnętrznej pętli, gdy deweloper pracuje nad kodem; zamiast tego zarówno dotnet ef dbcontext optimize, jak i dotnet publish można wykonać w przepływie pracy publikowania/wdrażania, w systemie ciągłej integracji/ciągłego wdrażania (CI/CD).

Uwaga

Publikowanie obecnie informuje o szeregu ostrzeżeń dotyczących trimmingu i ostrzeżeń związanych z NativeAOT, co oznacza, że nie można w pełni zagwarantować jej prawidłowego działania. Jest to oczekiwane, biorąc pod uwagę bieżący stan eksperymentalny obsługi NativeAOT; ostatnia funkcja nie eksperymentalna nie będzie zgłaszać żadnych ostrzeżeń.

Ograniczenia

Zapytania dynamiczne nie są obsługiwane

Wstępne kompilowanie zapytań wykonuje statyczną analizę kodu źródłowego, identyfikując zapytania EF LINQ i generując przechwytniki języka C#. LINQ umożliwia wyrażanie wysoce dynamicznych zapytań, w których operatory LINQ składają się na podstawie dowolnych warunków; Takie zapytania nie mogą być analizowane statycznie i obecnie nie są obsługiwane. Rozważmy następujący przykład:

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();
}

Powyższe zapytanie jest podzielone na kilka instrukcji i dynamicznie komponuje Where operator na podstawie parametru zewnętrznego. Takie zapytania nie mogą być wstępnie skompilowane. Czasami jednak można ponownie napisać takie zapytania dynamiczne jak wiele zapytań niedynamicznych:

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();

Ponieważ każde z tych dwóch zapytań może być statycznie analizowane od początku do końca, prekompilacja może się nimi zajmować.

Należy pamiętać, że zapytania dynamiczne będą prawdopodobnie obsługiwane w przyszłości podczas korzystania z funkcji NativeAOT; jednak ponieważ nie można ich wstępnie skompilować, będą nadal spowalniać uruchamianie aplikacji, a także zazwyczaj będą działać mniej wydajnie w porównaniu z wykonywaniem funkcji NativeAOT; Wynika to z faktu, że program EF wewnętrznie opiera się na generowaniu kodu w celu materializowania wyników bazy danych, ale generowanie kodu nie jest obsługiwane w przypadku korzystania z funkcji NativeAOT.

Inne ograniczenia

  • Składnia wyrażenia zapytania LINQ (czasami nazywana "składnią zrozumienia") nie jest obsługiwana.
  • Wygenerowany skompilowany model i przechwytywacze zapytań mogą obecnie być dość duże, jeśli chodzi o wielkość kodu, i ich generowanie może zająć dużo czasu. Planujemy to poprawić.
  • Dostawcy EF mogą potrzebować zaimplementować wsparcie dla wstępnie skompilowanych zapytań; sprawdź dokumentację dostawcy, aby dowiedzieć się, czy jest ona zgodna z obsługą NativeAOT w EF.
  • Konwertery wartości używające stanu przechwyconego nie są obsługiwane.

Wstępnie skompilowane zapytania bez funkcji NativeAOT

Ze względu na bieżące ograniczenia wsparcia NativeAOT w EF, może nie być użyteczne dla niektórych aplikacji. Można jednak korzystać ze wstępnie skompilowanych zapytań podczas publikowania zwykłych, nienatywnych aplikacji AOT; Dzięki temu można przynajmniej skorzystać z redukcji czasu uruchamiania, która oferuje wstępnie skompilowane zapytania, jednocześnie umożliwiając korzystanie z zapytań dynamicznych i innych funkcji, które nie są obecnie obsługiwane w przypadku funkcji NativeAOT.

Używanie wstępnie skompilowanych zapytań bez funkcji NativeAOT jest po prostu kwestią wykonywania następujących czynności:

dotnet ef dbcontext optimize --precompile-queries

Jak pokazano powyżej, spowoduje to wygenerowanie skompilowanego modelu i przechwytywaczy dla zapytań, które mogą zostać wstępnie skompilowane, co spowoduje zmniejszenie obciążenia przy uruchamianiu aplikacji.