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.
Method | 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.
Method | 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 [Posts] AS [b]
WHERE [b].[Name] = N'post1'
SELECT TOP(1) [b].[Id], [b].[Name]
FROM [Posts] AS [b]
WHERE [b].[Name] = N'post2'
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 [Posts] AS [b]
WHERE [b].[Name] = @__postTitle_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
Metryki platformy EF Core raportują współczynnik trafień pamięci podręcznej zapytań. W normalnej aplikacji ta metryka osiąga 100% wkrótce po uruchomieniu programu, po wykonaniu co najmniej raz większości zapytań. Jeśli ta metryka pozostanie stabilna 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
:
- 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).
- 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.
- 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:
Method | Ś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. Na 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. Na 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>
- Aby uzyskać więcej informacji, zobacz:
dotnet ef dbcontext optimize
. - Jeśli pracujesz w programie Visual Studio, możesz również użyć polecenia Optimize-DbContext
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. Na 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. Na 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:
- Filtry zapytań globalnych nie są obsługiwane.
- Serwery proxy z opóźnieniem ładowania i śledzenia zmian nie są obsługiwane.
- Model musi być synchronizowany ręcznie przez ponowne wygenerowanie go w dowolnym momencie zmiany definicji modelu lub konfiguracji.
- Niestandardowe implementacje IModelCacheKeyFactory nie są obsługiwane. Można jednak skompilować wiele modeli i załadować odpowiedni w razie potrzeby.
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żywaneDbContext
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ądzanieDbContext
pulą di wiąże się z niewielkim obciążeniem.
- Upewnij się, że
- 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.
- Równoczesne używanie tego samego