Tematy dotyczące wydajności zaawansowanej

Buforowanie obiektów DbContext

Obiekt DbContext jest zazwyczaj lekki: tworzenie i rozpowszechnianie nie wiąże się z operacją bazy danych, a większość aplikacji może to zrobić bez zauważalnego wpływu na wydajność. Jednak każde wystąpienie kontekstu konfiguruje różne wewnętrzne usługi i obiekty niezbędne do wykonywania swoich obowiązków, a nakład pracy ciągłej może być znaczący w scenariuszach o wysokiej wydajności. W takich przypadkach program EF Core może pulować wystąpienia kontekstu: po usunięciu kontekstu program EF Core resetuje jego stan i przechowuje go w puli wewnętrznej. Gdy nowe wystąpienie zostanie następnie żądane, zwracane jest wystąpienie w puli zamiast konfigurowania nowego. Buforowanie kontekstowe umożliwia płacenie kosztów konfiguracji kontekstu tylko raz podczas uruchamiania programu, a nie ciągłego.

Należy pamiętać, że buforowanie kontekstu jest ortogonalne z buforowaniem połączeń z bazą danych, które jest zarządzane na niższym poziomie w sterowniku bazy danych.

Typowy wzorzec w aplikacji ASP.NET Core przy użyciu programu EF Core obejmuje zarejestrowanie niestandardowego DbContext typu w kontenerze iniekcji zależności za pośrednictwem usługi AddDbContext. Następnie wystąpienia tego typu są uzyskiwane za pomocą parametrów konstruktora w kontrolerach lub na stronach Razor.

Aby włączyć buforowanie kontekstów, po prostu zastąp ciąg AddDbContext ciągiem AddDbContextPool:

builder.Services.AddDbContextPool<WeatherForecastContext>(
    o => o.UseSqlServer(builder.Configuration.GetConnectionString("WeatherForecastContext")));

Parametr poolSize ustawia AddDbContextPool maksymalną liczbę wystąpień zachowanych przez pulę (domyślnie 1024). Po przekroczeniu poolSize limitu nowe wystąpienia kontekstu nie są buforowane, a program EF wraca do zachowania niepulowania tworzenia wystąpień na żądanie.

Testy porównawcze

Poniżej przedstawiono wyniki testu porównawczego pobierania pojedynczego wiersza z bazy danych programu SQL Server uruchomionej lokalnie na tym samym komputerze z buforowaniem kontekstu i bez tego samego. Jak zawsze wyniki zmienią się wraz z liczbą wierszy, opóźnieniem serwera bazy danych i innymi czynnikami. Co ważne, te testy porównawcze mają wydajność jednowątkowego buforowania, podczas gdy rzeczywisty scenariusz może mieć różne wyniki; przed podjęciem jakichkolwiek decyzji na platformie należy wykonać testy porównawcze na platformie. Kod źródłowy jest dostępny tutaj, możesz używać go jako podstawy do własnych pomiarów.

Metoda Dzienniki numerów Średnia Błąd StdDev Gen 0 Pierwszej generacji Drugiej generacji Alokowane
Bez buforowania tekstu 1 701.6 nas 26.62 nas 78.48 us 11.7188 - - 50,38 KB
WithContextPooling 1 350.1 6.80 14.64 us 0.9766 - - 4.63 KB

Zarządzanie stanem w kontekstach w puli

Buforowanie kontekstu działa, ponownie używając tego samego wystąpienia kontekstu między żądaniami; Oznacza to, że jest on skutecznie zarejestrowany jako pojedynczy, a to samo wystąpienie jest ponownie używane w wielu żądaniach (lub zakresach DI). Oznacza to, że należy zachować szczególną ostrożność, gdy kontekst obejmuje dowolny stan, który może ulec zmianie między żądaniami. Co najważniejsze, kontekst jest wywoływany OnConfiguring tylko raz — po pierwszym utworzeniu kontekstu wystąpienia — i dlatego nie można użyć go do ustawienia stanu, który musi się różnić (np. identyfikator dzierżawy).

Typowy scenariusz obejmujący stan kontekstu to wielodostępna aplikacja ASP.NET Core, w której wystąpienie kontekstu ma identyfikator dzierżawy, który jest uwzględniany przez zapytania (zobacz Globalne filtry zapytań, aby uzyskać więcej szczegółów). Ponieważ identyfikator dzierżawy musi ulec zmianie przy użyciu każdego żądania internetowego, musimy wykonać kilka dodatkowych kroków, aby wszystko działało z buforowaniem kontekstów.

Załóżmy, że aplikacja rejestruje usługę o określonym zakresie ITenant , która opakowuje identyfikator dzierżawy i inne informacje związane z dzierżawą:

// Below is a minimal tenant resolution strategy, which registers a scoped ITenant service in DI.
// In this sample, we simply accept the tenant ID as a request query, which means that a client can impersonate any
// tenant. In a real application, the tenant ID would be set based on secure authentication data.
builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<ITenant>(sp =>
{
    var tenantIdString = sp.GetRequiredService<IHttpContextAccessor>().HttpContext.Request.Query["TenantId"];

    return tenantIdString != StringValues.Empty && int.TryParse(tenantIdString, out var tenantId)
        ? new Tenant(tenantId)
        : null;
});

Jak pisano powyżej, zwróć szczególną uwagę na to, skąd uzyskujesz identyfikator dzierżawy — jest to ważny aspekt zabezpieczeń aplikacji.

Gdy mamy usługę o określonym zakresie ITenant , zarejestruj fabrykę kontekstu puli jako usługę Singleton, jak zwykle:

builder.Services.AddPooledDbContextFactory<WeatherForecastContext>(
    o => o.UseSqlServer(builder.Configuration.GetConnectionString("WeatherForecastContext")));

Następnie napisz niestandardową fabrykę kontekstową, która pobiera kontekst w puli z zarejestrowanej fabryki Singleton i wprowadza identyfikator dzierżawy do wystąpień kontekstowych, które przekazuje:

public class WeatherForecastScopedFactory : IDbContextFactory<WeatherForecastContext>
{
    private const int DefaultTenantId = -1;

    private readonly IDbContextFactory<WeatherForecastContext> _pooledFactory;
    private readonly int _tenantId;

    public WeatherForecastScopedFactory(
        IDbContextFactory<WeatherForecastContext> pooledFactory,
        ITenant tenant)
    {
        _pooledFactory = pooledFactory;
        _tenantId = tenant?.TenantId ?? DefaultTenantId;
    }

    public WeatherForecastContext CreateDbContext()
    {
        var context = _pooledFactory.CreateDbContext();
        context.TenantId = _tenantId;
        return context;
    }
}

Po utworzeniu niestandardowej fabryki kontekstu zarejestruj ją jako usługę o określonym zakresie:

builder.Services.AddScoped<WeatherForecastScopedFactory>();

Na koniec zaaranżuj kontekst, który ma być wstrzykiwany z naszej fabryki zakresów:

builder.Services.AddScoped(
    sp => sp.GetRequiredService<WeatherForecastScopedFactory>().CreateDbContext());

W tym momencie kontrolery są automatycznie wstrzykiwane z wystąpieniem kontekstu, które ma prawidłowy identyfikator dzierżawy, bez konieczności znajomości czegokolwiek z tego powodu.

Pełny kod źródłowy dla tego przykładu jest dostępny tutaj.

Uwaga

Mimo że program EF Core zajmuje się resetowaniem stanu wewnętrznego dla DbContext i powiązanych usług, zazwyczaj nie resetuje stanu w bazowym sterowniku bazy danych, który znajduje się poza programem EF. Jeśli na przykład ręcznie otworzysz i użyjesz DbConnection stanu ADO.NET lub w inny sposób, musisz przywrócić ten stan przed zwróceniem wystąpienia kontekstu do puli, np. przez zamknięcie połączenia. Nie można tego zrobić, może spowodować wyciek stanu w niepowiązanych żądaniach.

Skompilowane zapytania

Gdy program EF odbiera drzewo zapytań LINQ do wykonania, musi najpierw "skompilować" to drzewo, np. utworzyć z niego kod SQL. Ponieważ to zadanie jest ciężkim procesem, program EF buforuje zapytania według kształtu drzewa zapytań, dzięki czemu zapytania z tą samą strukturą ponownie wewnętrznie buforowane dane wyjściowe kompilacji. To buforowanie gwarantuje, że wykonywanie tego samego zapytania LINQ wiele razy jest bardzo szybkie, nawet jeśli wartości parametrów są różne.

Jednak program EF musi nadal wykonywać pewne zadania, zanim będzie mógł korzystać z wewnętrznej pamięci podręcznej zapytań. Na przykład drzewo wyrażeń zapytania musi być rekursywnie porównywane z drzewami wyrażeń buforowanych zapytań, aby znaleźć poprawne buforowane zapytanie. Obciążenie związane z tym początkowym przetwarzaniem jest niewielkie w większości aplikacji EF, zwłaszcza w porównaniu z innymi kosztami związanymi z wykonywaniem zapytań (operacje we/wy sieci, rzeczywiste przetwarzanie zapytań i we/wy dysku w bazie danych...). Jednak w niektórych scenariuszach o wysokiej wydajności może być pożądane wyeliminowanie go.

Platforma EF obsługuje skompilowane zapytania, które umożliwiają jawną kompilację zapytania LINQ do delegata platformy .NET. Po uzyskaniu tego delegata można go wywołać bezpośrednio w celu wykonania zapytania bez podawania drzewa wyrażeń LINQ. Ta technika pomija wyszukiwanie pamięci podręcznej i zapewnia najbardziej zoptymalizowany sposób wykonywania zapytania w programie EF Core. Poniżej przedstawiono niektóre wyniki testów porównawczych porównujące skompilowane i niekompilowane wyniki zapytań; przed podjęciem jakichkolwiek decyzji na platformie należy wykonać testy porównawcze na platformie. Kod źródłowy jest dostępny tutaj, możesz używać go jako podstawy do własnych pomiarów.

Metoda Dzienniki numerów Średnia Błąd StdDev Gen 0 Alokowane
WithCompiledQuery 1 564.2 nas 6.75 us 5.99 1.9531 9 KB
Bez kolejkicompiledQuery 1 671.6 nas 12.72 nas 16.54 nas 2.9297 13 KB
WithCompiledQuery 10 645.3 nas 10.00 9,35 us 2.9297 13 KB
Bez kolejkicompiledQuery 10 709.8 nas 25.20 73.10 3.9063 18 KB

Aby użyć skompilowanych zapytań, najpierw skompiluj zapytanie w EF.CompileAsyncQuery następujący sposób (użyj polecenia EF.CompileQuery dla zapytań synchronicznych):

private static readonly Func<BloggingContext, int, IAsyncEnumerable<Blog>> _compiledQuery
    = EF.CompileAsyncQuery(
        (BloggingContext context, int length) => context.Blogs.Where(b => b.Url.StartsWith("http://") && b.Url.Length == length));

W tym przykładzie kodu udostępniamy platformę EF z wyrażeniem lambda akceptującym DbContext wystąpienie i dowolnym parametrem, który ma zostać przekazany do zapytania. Teraz możesz wywołać ten delegat za każdym razem, gdy chcesz wykonać zapytanie:

await foreach (var blog in _compiledQuery(context, 8))
{
    // Do something with the results
}

Należy pamiętać, że delegat jest bezpieczny wątkowo i może być wywoływany współbieżnie w różnych wystąpieniach kontekstu.

Ograniczenia

  • Skompilowane zapytania mogą być używane tylko dla pojedynczego modelu EF Core. Czasami można skonfigurować różne wystąpienia kontekstu tego samego typu do używania różnych modeli; uruchamianie skompilowanych zapytań w tym scenariuszu nie jest obsługiwane.
  • W przypadku używania parametrów w skompilowanych zapytaniach użyj prostych, skalarnych parametrów. Bardziej złożone wyrażenia parametrów — takie jak dostęp do elementu członkowskiego/metody w wystąpieniach — nie są obsługiwane.

Buforowanie zapytań i parametryzacja

Gdy program EF odbiera drzewo zapytań LINQ do wykonania, musi najpierw "skompilować" to drzewo, np. utworzyć z niego kod SQL. Ponieważ to zadanie jest ciężkim procesem, program EF buforuje zapytania według kształtu drzewa zapytań, dzięki czemu zapytania z tą samą strukturą ponownie wewnętrznie buforowane dane wyjściowe kompilacji. To buforowanie gwarantuje, że wykonywanie tego samego zapytania LINQ wiele razy jest bardzo szybkie, nawet jeśli wartości parametrów są różne.

Rozważ następujące dwa zapytania:

var post1 = context.Posts.FirstOrDefault(p => p.Title == "post1");
var post2 = context.Posts.FirstOrDefault(p => p.Title == "post2");

Ponieważ drzewa wyrażeń zawierają różne stałe, drzewo wyrażeń różni się, a każde z tych zapytań zostanie skompilowane oddzielnie przez program EF Core. Ponadto każde zapytanie tworzy nieco inne polecenie SQL:

SELECT TOP(1) [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Name] = N'blog1'

SELECT TOP(1) [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Name] = N'blog2'

Ponieważ baza danych SQL różni się, serwer bazy danych prawdopodobnie będzie również musiał utworzyć plan zapytania dla obu zapytań, zamiast ponownie używać tego samego planu.

Niewielka modyfikacja zapytań może znacznie zmienić następujące elementy:

var postTitle = "post1";
var post1 = context.Posts.FirstOrDefault(p => p.Title == postTitle);
postTitle = "post2";
var post2 = context.Posts.FirstOrDefault(p => p.Title == postTitle);

Ponieważ nazwa bloga jest teraz sparametryzowana, oba zapytania mają ten sam kształt drzewa, a program EF musi zostać skompilowany tylko raz. Wygenerowany program SQL jest również sparametryzowany, co umożliwia bazie danych ponowne użycie tego samego planu zapytania:

SELECT TOP(1) [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Name] = @__blogName_0

Należy pamiętać, że nie ma potrzeby sparametryzowania każdego zapytania: doskonale sprawdza się, czy niektóre zapytania z stałymi, a nawet bazy danych (i EF) mogą czasami wykonywać pewne optymalizacje wokół stałych, które nie są możliwe, gdy zapytanie jest sparametryzowane. Zobacz sekcję dotyczącą dynamicznie skonstruowanych zapytań , aby zapoznać się z przykładem, w którym kluczowa jest właściwa parametryzacja.

Uwaga

Liczniki zdarzeń platformy EF Core zgłaszają współczynnik trafień pamięci podręcznej zapytań. W normalnej aplikacji ten licznik osiąga 100% wkrótce po uruchomieniu programu, po wykonaniu co najmniej raz większości zapytań. Jeśli ten licznik pozostanie stabilny poniżej 100%, oznacza to, że aplikacja może wykonywać coś, co powoduje pokonanie pamięci podręcznej zapytań — warto to zbadać.

Uwaga

Sposób, w jaki baza danych zarządza planami zapytań pamięci podręcznej, jest zależna od bazy danych. Na przykład program SQL Server niejawnie utrzymuje pamięć podręczną planu zapytania LRU, natomiast baza danych PostgreSQL nie (ale przygotowane instrukcje mogą spowodować bardzo podobny efekt końcowy). Aby uzyskać więcej informacji, zapoznaj się z dokumentacją bazy danych.

Zapytania tworzone dynamicznie

W niektórych sytuacjach konieczne jest dynamiczne konstruowanie zapytań LINQ, a nie określanie ich wprost w kodzie źródłowym. Może się to zdarzyć na przykład w witrynie internetowej, która odbiera dowolne szczegóły zapytania od klienta, z operatorami zapytań typu open-end (sortowanie, filtrowanie, stronicowanie...). W zasadzie, jeśli wykonywane poprawnie, dynamicznie skonstruowane zapytania mogą być równie wydajne jak zwykłe (chociaż nie jest możliwe użycie skompilowanej optymalizacji zapytań z zapytaniami dynamicznymi). W praktyce jednak często są one źródłem problemów z wydajnością, ponieważ łatwo jest przypadkowo tworzyć drzewa wyrażeń z kształtami, które różnią się za każdym razem.

W poniższym przykładzie użyto trzech technik do konstruowania wyrażenia lambda zapytania Where :

  1. Interfejs API wyrażeń ze stałą: dynamicznie kompiluj wyrażenie za pomocą interfejsu API wyrażeń przy użyciu węzła stałego. Jest to częsty błąd podczas dynamicznego tworzenia drzew wyrażeń i powoduje, że program EF ponownie skompiluje zapytanie za każdym razem, gdy jest wywoływana z inną stałą wartością (zwykle powoduje również zanieczyszczenie pamięci podręcznej planu na serwerze bazy danych).
  2. Interfejs API wyrażeń z parametrem: lepsza wersja, która zastępuje stałą parametrem . Gwarantuje to, że zapytanie jest kompilowane tylko raz, niezależnie od podanej wartości, a ten sam (sparametryzowany) sql jest generowany.
  3. Proste z parametrem: wersja, która nie używa interfejsu API wyrażeń, dla porównania tworzy to samo drzewo, co metoda powyżej, ale jest znacznie prostsze. W wielu przypadkach można dynamicznie tworzyć drzewo wyrażeń bez uciekania się do interfejsu API wyrażeń, co jest łatwe do błędu.

Where Dodajemy operator do zapytania tylko wtedy, gdy dany parametr nie ma wartości null. Należy pamiętać, że nie jest to dobry przypadek użycia dynamicznego konstruowania zapytania , ale używamy go dla uproszczenia:

[Benchmark]
public int ExpressionApiWithConstant()
{
    var url = "blog" + Interlocked.Increment(ref _blogNumber);
    using var context = new BloggingContext();

    IQueryable<Blog> query = context.Blogs;

    if (_addWhereClause)
    {
        var blogParam = Expression.Parameter(typeof(Blog), "b");
        var whereLambda = Expression.Lambda<Func<Blog, bool>>(
            Expression.Equal(
                Expression.MakeMemberAccess(
                    blogParam,
                    typeof(Blog).GetMember(nameof(Blog.Url)).Single()),
                Expression.Constant(url)),
            blogParam);

        query = query.Where(whereLambda);
    }

    return query.Count();
}

Wykonanie testów porównawczych tych dwóch technik daje następujące wyniki:

Metoda Średnia Błąd StdDev Gen0 Gen1 Alokowane
ExpressionApiWithConstant 1665.8 56.99 nas 163.5 nas 15.6250 - 109,92 KB
ExpressionApiWithParameter 757.1 nas 35.14 nas 103.6 12.6953 0.9766 54,95 KB
SimpleWithParameter 760.3 nas 37.99 nas 112.0 12.6953 - 55,03 KB

Nawet jeśli różnica w milisekundach wydaje się niewielka, należy pamiętać, że stała wersja stale zanieczyszcza pamięć podręczną i powoduje ponowne skompilowanie innych zapytań, spowalniając je również i mając ogólny negatywny wpływ na ogólną wydajność. Zdecydowanie zaleca się unikanie ciągłej ponownej kompilacji zapytań.

Uwaga

Unikaj konstruowania zapytań przy użyciu interfejsu API drzewa wyrażeń, chyba że jest to naprawdę konieczne. Oprócz złożoności interfejsu API bardzo łatwo jest przypadkowo powodować istotne problemy z wydajnością.

Skompilowane modele

Skompilowane modele mogą poprawić czas uruchamiania platformy EF Core dla aplikacji z dużymi modelami. Duży model zwykle oznacza setki do tysięcy typów jednostek i relacji. Czas uruchamiania to czas, aby wykonać pierwszą operację na DbContext obiekcie, gdy ten DbContext typ jest używany po raz pierwszy w aplikacji. Pamiętaj, że utworzenie DbContext wystąpienia nie powoduje zainicjowania modelu EF. Zamiast tego typowe pierwsze operacje, które powodują zainicjowanie modelu, obejmują wywołanie DbContext.Add lub wykonanie pierwszego zapytania.

Skompilowane modele są tworzone przy użyciu dotnet ef narzędzia wiersza polecenia. Przed kontynuowaniem upewnij się, że zainstalowano najnowszą wersję narzędzia .

Nowe dbcontext optimize polecenie służy do generowania skompilowanego modelu. Przykład:

dotnet ef dbcontext optimize

Opcje --output-dir i --namespace mogą służyć do określania katalogu i przestrzeni nazw, w której zostanie wygenerowany skompilowany model. Przykład:

PS C:\dotnet\efdocs\samples\core\Miscellaneous\CompiledModels> dotnet ef dbcontext optimize --output-dir MyCompiledModels --namespace MyCompiledModels
Build started...
Build succeeded.
Successfully generated a compiled model, to use it call 'options.UseModel(MyCompiledModels.BlogsContextModel.Instance)'. Run this command again when the model is modified.
PS C:\dotnet\efdocs\samples\core\Miscellaneous\CompiledModels>

Dane wyjściowe z uruchomienia tego polecenia zawierają fragment kodu do kopiowania i wklejania do DbContext konfiguracji, aby spowodować użycie skompilowanego modelu programu EF Core. Przykład:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseModel(MyCompiledModels.BlogsContextModel.Instance)
        .UseSqlite(@"Data Source=test.db");

Uruchamianie skompilowanego modelu

Zazwyczaj nie jest konieczne przyjrzenie się wygenerowanemu kodowi uruchamiania. Czasami jednak może być przydatne dostosowanie modelu lub jego ładowania. Kod bootstrapping wygląda następująco:

[DbContext(typeof(BlogsContext))]
partial class BlogsContextModel : RuntimeModel
{
    private static BlogsContextModel _instance;
    public static IModel Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new BlogsContextModel();
                _instance.Initialize();
                _instance.Customize();
            }

            return _instance;
        }
    }

    partial void Initialize();

    partial void Customize();
}

Jest to klasa częściowa z metodami częściowymi, które można zaimplementować w celu dostosowania modelu zgodnie z potrzebami.

Ponadto można wygenerować wiele skompilowanych modeli dla DbContext typów, które mogą używać różnych modeli w zależności od konfiguracji środowiska uruchomieniowego. Powinny one zostać umieszczone w różnych folderach i przestrzeniach nazw, jak pokazano powyżej. Informacje o środowisku uruchomieniowym, takie jak parametry połączenia, można następnie zbadać i zwrócić prawidłowy model zgodnie z potrzebami. Przykład:

public static class RuntimeModelCache
{
    private static readonly ConcurrentDictionary<string, IModel> _runtimeModels
        = new();

    public static IModel GetOrCreateModel(string connectionString)
        => _runtimeModels.GetOrAdd(
            connectionString, cs =>
            {
                if (cs.Contains("X"))
                {
                    return BlogsContextModel1.Instance;
                }

                if (cs.Contains("Y"))
                {
                    return BlogsContextModel2.Instance;
                }

                throw new InvalidOperationException("No appropriate compiled model found.");
            });
}

Ograniczenia

Skompilowane modele mają pewne ograniczenia:

Ze względu na te ograniczenia należy używać tylko skompilowanych modeli, jeśli czas uruchamiania platformy EF Core jest zbyt wolny. Kompilowanie małych modeli zwykle nie jest warte.

Jeśli obsługa dowolnej z tych funkcji ma kluczowe znaczenie dla twojego sukcesu, zagłosuj na odpowiednie problemy połączone powyżej.

Zmniejszenie obciążenia środowiska uruchomieniowego

Podobnie jak w przypadku każdej warstwy, platforma EF Core dodaje nieco nakładu pracy w czasie wykonywania w porównaniu do kodowania bezpośrednio względem interfejsów API baz danych niższego poziomu. To obciążenie środowiska uruchomieniowego jest mało prawdopodobne, aby miało znaczący wpływ na większość rzeczywistych aplikacji; Inne tematy w tym przewodniku wydajności, takie jak wydajność zapytań, użycie indeksu i minimalizowanie pasków, są znacznie ważniejsze. Ponadto nawet w przypadku wysoce zoptymalizowanych aplikacji opóźnienie sieci i operacje we/wy bazy danych zwykle dominuje w każdym czasie spędzonym wewnątrz samego programu EF Core. Jednak w przypadku aplikacji o wysokiej wydajności i małych opóźnieniach, w których każda część wydajności jest ważna, następujące zalecenia mogą służyć do zmniejszenia obciążenia platformy EF Core do minimum:

  • Włącz buforowanie dbContext. Nasze testy porównawcze pokazują, że ta funkcja może mieć decydujący wpływ na aplikacje o wysokiej wydajności i małych opóźnieniach.
    • Upewnij się, że maxPoolSize scenariusz użycia odpowiada scenariuszowi użycia. Jeśli jest on zbyt niski, DbContext wystąpienia będą stale tworzone i usuwane, obniżając wydajność. Ustawienie zbyt dużej ilości może niepotrzebnie zużywać pamięć, ponieważ nieużywane DbContext wystąpienia są przechowywane w puli.
    • Aby zwiększyć dodatkową niewielką liczbę wydajności, rozważ użycie PooledDbContextFactory zamiast bezpośredniego wstrzykiwania di wystąpień kontekstu. Zarządzanie DbContext pulą di wiąże się z niewielkim obciążeniem.
  • Użyj wstępnie skompilowanych zapytań dla zapytań gorących.
    • Im bardziej złożone zapytanie LINQ — tym więcej operatorów zawiera, a im większe, wynikowe drzewo wyrażeń — więcej zysków można oczekiwać od użycia skompilowanych zapytań.
  • Rozważ wyłączenie kontroli bezpieczeństwa wątków, ustawiając wartość EnableThreadSafetyChecks false w konfiguracji kontekstu.
    • Równoczesne używanie tego samego DbContext wystąpienia z różnych wątków nie jest obsługiwane. Program EF Core ma funkcję bezpieczeństwa, która wykrywa tę usterkę programowania w wielu przypadkach (ale nie wszystkie) i natychmiast zgłasza wyjątek informacyjny. Jednak ta funkcja bezpieczeństwa dodaje pewne obciążenie środowiska uruchomieniowego.
    • OSTRZEŻENIE: Po dokładnym przetestowaniu, czy aplikacja nie zawiera takich usterek współbieżności, wyłącz tylko sprawdzanie bezpieczeństwa wątków.