DbContext — okres istnienia, konfiguracja i inicjowanie

W tym artykule przedstawiono podstawowe wzorce inicjowania i konfiguracji wystąpienia DbContext.

Okres istnienia elementu DbContext

Okres istnienia elementu DbContext rozpoczyna się po utworzeniu wystąpienia i kończy się po usunięciu tego wystąpienia. Wystąpienie DbContext jest przeznaczone do użycia na potrzeby jednejjednostki pracy. Oznacza to, że okres istnienia wystąpienia DbContext jest zwykle bardzo krótki.

Napiwek

Cytując Martina Fowlera z powyższego linku, „Jednostka pracy śledzi wszystko, co robisz podczas transakcji biznesowej, co może wpłynąć na bazę danych. Kiedy skończysz, oblicza wszystko, co należy zrobić, aby zmienić bazę danych będącą wynikiem pracy.”

Typowa jednostka pracy podczas korzystania z platformy Entity Framework Core (EF Core) obejmuje:

  • Utworzenie wystąpienia DbContext
  • Śledzenie wystąpień jednostek według kontekstu. Jednostki są śledzone przez
  • Zmiany są wprowadzane do śledzonych jednostek zgodnie z potrzebami w celu zaimplementowania reguły biznesowej
  • wywoływana jest metoda SaveChanges lub SaveChangesAsync. Platforma EF Core wykrywa wprowadzone zmiany i zapisuje je w bazie danych.
  • Wystąpienie DbContext jest usuwane

Ważne

  • Bardzo ważne jest, aby pozbyć się elementu DbContext po jego użyciu. Gwarantuje to, że wszystkie niezarządzane zasoby zostaną zwolnione, oraz że wszystkie zdarzenia lub inne punkty zaczepienia zostaną wyrejestrowane, aby zapobiec przeciekom pamięci w przypadku, gdy wystąpienie nadal będzie wywoływane.
  • Kontekst DbContext nie jest bezpieczny wątkowo. Nie udostępniaj kontekstów między wątkami. Pamiętaj, aby użyć instrukcji await dla wszystkich wywołań asynchronicznych przed kontynuowaniem korzystania z wystąpienia kontekstu.
  • Wyjątek InvalidOperationException zgłaszany przez kod platformy EF Core może umieścić kontekst w stanie uniemożliwiającym odzyskanie. Takie wyjątki wskazują na błąd programu i nie są przeznaczone do odzyskania.

Kontekst DbContext we wstrzykiwaniu zależności dla platformy ASP.NET Core

W wielu aplikacjach internetowych każde żądanie HTTP odpowiada pojedynczej jednostce pracy. Dzięki temu powiązanie okresu istnienia kontekstu z okresem istnienia żądania jest dobrym ustawieniem domyślnym dla aplikacji internetowych.

Aplikacje platformy ASP.NET Core są konfigurowane przy użyciu wstrzykiwania zależności. Platformę EF Core można dodać do tej konfiguracji przy użyciu elementu AddDbContext w metodzie ConfigureServices pliku Startup.cs. Przykład:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    services.AddDbContext<ApplicationDbContext>(
        options => options.UseSqlServer("name=ConnectionStrings:DefaultConnection"));
}

W tym przykładzie zarejestrowano podklasę DbContext o nazwie ApplicationDbContext jako usługę o określonym zakresie w dostawcy usług aplikacji ASP.NET Core (czyli kontenera wstrzykiwania zależności). Kontekst jest skonfigurowany do używania dostawcy bazy danych SQL Server i odczyta on parametry połączenia z konfiguracji platformy ASP.NET Core. Zwykle nie ma znaczenia, gdzie w elemencie ConfigureServices wywoływany jest kontekst AddDbContext.

Klasa ApplicationDbContext musi uwidocznić konstruktor publiczny z parametrem DbContextOptions<ApplicationDbContext>. W ten sposób konfiguracja kontekstu z elementu AddDbContext jest przekazywana do elementu DbContext. Przykład:

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }
}

Element ApplicationDbContextmoże być następnie używany w kontrolerach ASP.NET Core lub innych usługach za pomocą wstrzykiwania konstruktora. Przykład:

public class MyController
{
    private readonly ApplicationDbContext _context;

    public MyController(ApplicationDbContext context)
    {
        _context = context;
    }
}

Wynik końcowy to wystąpienie ApplicationDbContext utworzone dla każdego żądania i przekazane do kontrolera w celu wykonania jednostki pracy, po czym wystąpienie to jest usuwane w momencie zakończenia żądania.

Przeczytaj dalszą część tego artykułu, aby dowiedzieć się więcej o opcjach konfiguracji. Ponadto zobacz Uruchamianie aplikacji na platformie ASP.NET Core i Wstrzykiwanie zależności na platformie ASP.NET Core, aby uzyskać więcej informacji na temat konfigurowania i wstrzykiwania zależności na platformie ASP.NET Core.

Proste inicjowanie elementu DbContext przy użyciu polecenia „new”

Wystąpienia DbContext można tworzyć w normalny sposób platformy .NET, na przykład przy użyciu polecenia new w języku C#. Konfigurację można wykonać przez zastąpienie metody OnConfiguring lub przekazanie opcji do konstruktora. Przykład:

public class ApplicationDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test");
    }
}

Ten wzorzec ułatwia również przekazywanie konfiguracji, takiej jak parametry połączenia za pomocą konstruktora DbContext. Przykład:

public class ApplicationDbContext : DbContext
{
    private readonly string _connectionString;

    public ApplicationDbContext(string connectionString)
    {
        _connectionString = connectionString;
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(_connectionString);
    }
}

Alternatywnie elementu DbContextOptionsBuilder można użyć do utworzenia obiektu DbContextOptions, który jest następnie przekazywany do konstruktora DbContext. Dzięki temu można także jawnie skonstruować element DbContext skonfigurowany pod kątem wstrzykiwania zależności. Na przykład w przypadku użycia elementu ApplicationDbContext zdefiniowanego dla aplikacji internetowych platformy ASP.NET Core powyżej:

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }
}

Można utworzyć element DbContextOptions i jawne wywołać konstruktora:

var contextOptions = new DbContextOptionsBuilder<ApplicationDbContext>()
    .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test")
    .Options;

using var context = new ApplicationDbContext(contextOptions);

Korzystanie z fabryki DbContext (np. dla platformy Blazor)

Niektóre typy aplikacji (np. ASP.NET Core Blazor) używają wstrzykiwania zależności, ale nie tworzą zakresu usługi zgodnego z żądanym okresem istnienia elementu DbContext. Nawet wtedy, gdy taka zgodność istnieje, może być konieczne wykonanie wielu jednostek pracy w tym zakresie. Na przykład wiele jednostek pracy w ramach pojedynczego żądania HTTP.

W takich przypadkach przy użyciu elementu AddDbContextFactory można zarejestrować fabrykę na potrzeby tworzenia wystąpień DbContext. Przykład:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContextFactory<ApplicationDbContext>(
        options =>
            options.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test"));
}

Klasa ApplicationDbContext musi uwidocznić konstruktor publiczny z parametrem DbContextOptions<ApplicationDbContext>. Jest to ten sam wzorzec co w sekcji dotyczącej tradycyjnej platformy ASP.NET Core powyżej.

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    {
    }
}

Fabryki DbContextFactory można następnie używać w innych usługach za pomocą wstrzyknięcia konstruktora. Przykład:

private readonly IDbContextFactory<ApplicationDbContext> _contextFactory;

public MyController(IDbContextFactory<ApplicationDbContext> contextFactory)
{
    _contextFactory = contextFactory;
}

Za pomocą wstrzykniętej fabryki można następnie utworzyć wystąpienia DbContext w kodzie usługi. Przykład:

public void DoSomething()
{
    using (var context = _contextFactory.CreateDbContext())
    {
        // ...
    }
}

Zwróć uwagę, że wystąpienia DbContext utworzone w ten sposób nie są zarządzane przez dostawcę usług aplikacji i dlatego muszą zostać usunięte przez aplikację.

Aby uzyskać więcej informacji na temat korzystania z platformy EF Core z platformą Blazor, zobacz Serwer ASP.NET Core Blazor Server z platformą Entity Framework Core.

DbContextOptions

Punktem początkowym dla całej konfiguracji elementu DbContext jest element DbContextOptionsBuilder. Tego konstruktora można uzyskać na trzy sposoby:

  • W elemencie AddDbContext i powiązanych metodach
  • W elemencie OnConfiguring
  • Jawnie skonstruowane za pomocą polecenia new

Przykłady każdego z nich są dostępne w poprzednich sekcjach. Tę samą konfigurację można zastosować niezależnie od tego, skąd pochodzi konstruktor. Ponadto element OnConfiguring zawsze jest wywoływany niezależnie od sposobu konstruowania kontekstu. Oznacza to, że za pomocą elementu OnConfiguring można wykonać dodatkową konfigurację nawet wtedy, gdy element AddDbContext jest używany.

Konfigurowanie dostawcy bazy danych

Każde wystąpienie elementu DbContext musi być skonfigurowane do używania tylko jednego dostawcy bazy danych. (Różne wystąpienia podtypu DbContext mogą być używane z różnymi dostawcami baz danych, ale jedno wystąpienie musi używać tylko jednego dostawcy). Dostawca bazy danych jest konfigurowany przy użyciu określonego wywołania metody Use*. Aby na przykład użyć dostawcy bazy danych SQL Server:

public class ApplicationDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test");
    }
}

Te metody Use* są metodami rozszerzeń implementowanymi przez dostawcę bazy danych. Oznacza to, że przed zastosowaniem metody rozszerzenia należy zainstalować pakiet NuGet dostawcy bazy danych.

Napiwek

Dostawcy baz danych platformy EF Core często korzystają z metod rozszerzeń. Jeśli kompilator wskazuje, że nie można odnaleźć metody, upewnij się, że pakiet NuGet dostawcy jest zainstalowany i że znajduje w kodzie znajduje się element using Microsoft.EntityFrameworkCore;.

Poniższa tabela zawiera przykłady dla typowych dostawców baz danych.

System bazy danych Przykładowa konfiguracja Pakiet NuGet
SQL Server lub Azure SQL .UseSqlServer(connectionString) Microsoft.EntityFrameworkCore.SqlServer
Azure Cosmos DB .UseCosmos(connectionString, databaseName) Microsoft.EntityFrameworkCore.Cosmos
SQLite .UseSqlite(connectionString) Microsoft.EntityFrameworkCore.Sqlite
Baza danych EF Core w pamięci .UseInMemoryDatabase(databaseName) Microsoft.EntityFrameworkCore.InMemory
PostgreSQL* .UseNpgsql(connectionString) Npgsql.EntityFrameworkCore.PostgreSQL
MySQL/MariaDB* .UseMySql(connectionString) Pomelo.EntityFrameworkCore.MySql
Oracle* .UseOracle(connectionString) Oracle.EntityFrameworkCore

*Ci dostawcy baz danych nie są dostarczani przez firmę Microsoft. Aby uzyskać więcej informacji na temat dostawców baz danych, zobacz Dostawcy baz danych.

Ostrzeżenie

Baza danych EF Core w pamięci nie jest przeznaczona do użytku w środowisku produkcyjnym. Ponadto może nie być najlepszym wyborem nawet do testowania. Aby uzyskać więcej informacji, zobacz Testowanie kodu korzystającego z platformy EF Core.

Aby uzyskać więcej informacji na temat używania parametrów połączenia z platformą EF Core, zobacz Parametry połączenia.

Opcjonalna konfiguracja specyficzna dla dostawcy bazy danych jest wykonywana w dodatkowym konstruktorze specyficznym dla dostawcy. Na przykład użycie polecenia EnableRetryOnFailure, aby skonfigurować ponawianie prób pod kątem elastyczności połączenia podczas nawiązywania połączenia z bazą danych Azure SQL:

public class ApplicationDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .UseSqlServer(
                @"Server=(localdb)\mssqllocaldb;Database=Test",
                providerOptions => { providerOptions.EnableRetryOnFailure(); });
    }
}

Napiwek

Ten sam dostawca bazy danych jest używany dla baz danych SQl Server i Azure SQL. Zaleca się jednak, aby podczas nawiązywania połączenia z usługą SQL Azure używać elastyczności połączenia.

Aby uzyskać więcej informacji na temat konfiguracji specyficznej dla dostawcy, zobacz Dostawcy baz danych.

Inna konfiguracja DbContext

Inną konfigurację DbContext można połączyć w łańcuch przed wywołaniem Use* lub po nim (nie ma to znaczenia). Aby na przykład włączyć rejestrowanie danych poufnych:

public class ApplicationDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .EnableSensitiveDataLogging()
            .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test");
    }
}

Poniższa tabela zawiera przykłady typowych metod wywoływanych w elemencie DbContextOptionsBuilder.

Metoda DbContextOptionsBuilder Wyniki działania Dowiedz się więcej
UseQueryTrackingBehavior Ustawia domyślne zachowanie śledzenia dla zapytań Zachowanie śledzenia zapytań
LogTo Prosty sposób uzyskiwania dzienników programu EF Core Rejestrowanie, zdarzenia i diagnostyka
UseLoggerFactory Rejestruje fabrykę Microsoft.Extensions.Logging Rejestrowanie, zdarzenia i diagnostyka
EnableSensitiveDataLogging Uwzględnia dane aplikacji w wyjątkach i rejestrowaniu Rejestrowanie, zdarzenia i diagnostyka
EnableDetailedErrors Bardziej szczegółowe błędy zapytań (kosztem wydajności) Rejestrowanie, zdarzenia i diagnostyka
ConfigureWarnings Ignoruj lub zgłaszaj ostrzeżenia i inne zdarzenia Rejestrowanie, zdarzenia i diagnostyka
AddInterceptors Rejestruje interceptory platformy EF Core Rejestrowanie, zdarzenia i diagnostyka
UseLazyLoadingProxies Używaj dynamicznych serwerów proxy do ładowania opóźnionego Ładowanie opóźnione
UseChangeTrackingProxies Używaj dynamicznych serwerów proxy do śledzenia zmian Wkrótce...

Uwaga

UseLazyLoadingProxies i UseChangeTrackingProxies są metodami rozszerzeń z pakietu NuGet Microsoft.EntityFrameworkCore.Proxies. Tego rodzaju wywołanie „.UseSomething()” jest zalecanym sposobem konfigurowania i/lub używania rozszerzeń platformy EF Core zawartych w innych pakietach.

DbContextOptions a DbContextOptions<TContext>

Większość podklas DbContext, które akceptują element DbContextOptions, powinny używać odmiany ogólnejDbContextOptions<TContext>. Przykład:

public sealed class SealedApplicationDbContext : DbContext
{
    public SealedApplicationDbContext(DbContextOptions<SealedApplicationDbContext> contextOptions)
        : base(contextOptions)
    {
    }
}

Gwarantuje to, że prawidłowe opcje określonego podtypu DbContext są rozpoznawane ze wstrzyknięcia zależności, nawet jeśli zarejestrowano wiele podtypów DbContext.

Napiwek

Element DbContext nie musi być zapieczętowany, ale zapieczętowanie jest najlepszym rozwiązaniem w przypadku klas, które nie są przeznaczone do dziedziczenia.

Jeśli jednak sam podtyp DbContext ma być dziedziczony, powinien uwidaczniać konstruktor chroniony, który przyjmuje nieogólny element DbContextOptions. Przykład:

public abstract class ApplicationDbContextBase : DbContext
{
    protected ApplicationDbContextBase(DbContextOptions contextOptions)
        : base(contextOptions)
    {
    }
}

Umożliwia to wielu konkretnym podklasom wywołanie tego konstruktora podstawowego przy użyciu różnych wystąpień ogólnych elementu DbContextOptions<TContext>. Przykład:

public sealed class ApplicationDbContext1 : ApplicationDbContextBase
{
    public ApplicationDbContext1(DbContextOptions<ApplicationDbContext1> contextOptions)
        : base(contextOptions)
    {
    }
}

public sealed class ApplicationDbContext2 : ApplicationDbContextBase
{
    public ApplicationDbContext2(DbContextOptions<ApplicationDbContext2> contextOptions)
        : base(contextOptions)
    {
    }
}

Zwróć uwagę, że jest to dokładnie taki sam wzorzec co w przypadku dziedziczenia bezpośrednio z elementu DbContext. Oznacza to, że sam konstruktor DbContext akceptuje nieogólny element DbContextOptions z tego powodu.

Podklasa DbContext przeznaczona do utworzenia wystąpienia i dziedziczenia powinna uwidocznić obie formy konstruktora. Przykład:

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> contextOptions)
        : base(contextOptions)
    {
    }

    protected ApplicationDbContext(DbContextOptions contextOptions)
        : base(contextOptions)
    {
    }
}

Konfiguracja DbContext w czasie projektowania

Narzędzia czasu projektowania platformy EF Core, takie jak te dotyczące migracji platformy EF Core, muszą być w stanie odnaleźć i utworzyć działające wystąpienie typu DbContext, aby zebrać szczegółowe informacje o typach jednostek aplikacji i sposobie mapowania ich na schemat bazy danych. Ten proces może być automatyczny, jeśli tylko narzędzie może łatwo utworzyć element DbContext w taki sposób, że zostanie on skonfigurowany podobnie do sposobu konfigurowania w czasie wykonywania.

Chociaż każdy wzorzec, który dostarcza niezbędnych informacji o konfiguracji do elementu DbContext, może działać w czasie wykonywania, narzędzia wymagające używania elementu DbContext w czasie projektowania mogą działać tylko z ograniczoną liczbą wzorców. Zostały one szczegółowo omówione w temacie Tworzenie kontekstu w czasie projektowania.

Unikanie problemów z wątkami DbContext

Platforma Entity Framework Core nie obsługuje wielu równoległych operacji uruchamianych w tym samym wystąpieniu DbContext. Obejmuje to zarówno równoległe wykonywanie zapytań asynchronicznych, jak i wszelkie jawne współbieżne użycie z wielu wątków. W związku z tym zawsze natychmiast używaj instrukcji await dla wywołań asynchronicznych lub używaj oddzielnych wystąpień DbContext dla operacji wykonywanych równolegle.

Gdy platforma EF Core wykryje próbę współbieżnego użycia wystąpienia DbContext, zostanie wyświetlony wyjątek InvalidOperationException z komunikatem podobnym do następującego:

Druga operacja rozpoczęła się w tym kontekście przed ukończeniem poprzedniej operacji. Jest to zwykle spowodowane przez różne wątki korzystające z tego samego wystąpienia DbContext, jednak nie ma gwarancji, że elementy członkowskie są bezpieczne wątkowo.

Gdy współbieżny dostęp nie zostanie wykryty, może to spowodować niezdefiniowane zachowanie, awarie aplikacji i uszkodzenie danych.

Istnieją typowe błędy, które mogą przypadkowo powodować współbieżny dostęp w tym samym wystąpieniu DbContext:

Pułapki operacji asynchronicznych

Metody asynchroniczne umożliwiają platformie EF Core inicjowanie operacji, które uzyskują dostęp do bazy danych w sposób nieblokujący. Ale jeśli obiekt wywołujący nie oczekuje na ukończenie jednej z tych metod i kontynuuje wykonywanie innych operacji na elemencie DbContext, stan elementu DbContext może być (i bardzo prawdopodobnie będzie) uszkodzony.

Zawsze natychmiast używaj instrukcji await dla metod asynchronicznych platformy EF Core.

Niejawne udostępnianie wystąpień DbContext za pośrednictwem wstrzykiwania zależności

Metoda rozszerzenia AddDbContext domyślnie rejestruje typy DbContext z okresem istnienia o określonym zakresie.

Nie powoduje to problemów z dostępem współbieżnym w większości aplikacji ASP.NET Core, ponieważ istnieje tylko jeden wątek wykonujący każde żądanie klienta w danym momencie, a ponieważ każde żądanie otrzymuje oddzielny zakres wstrzykiwania zależności (a w związku z tym oddzielne wystąpienie DbContext). W przypadku modelu hostingu Blazor Server jedno żądanie logiczne jest używane do obsługi obwodu użytkownika Blazor, a tym samym tylko jedno wystąpienie DbContext o określonym zakresie jest dostępne dla każdego obwodu użytkownika, jeśli jest używany domyślny zakres wstrzykiwania.

Każdy kod, który jawnie wykonuje wiele wątków równolegle, powinien zapewniać, że wystąpienia DbContext nie są nigdy używane współbieżnie.

Można to osiągnąć za pomocą wstrzykiwania zależności, rejestrując kontekst jako kontekst z zakresem i tworząc zakresy (przy użyciu elementu IServiceScopeFactory) dla każdego wątku lub rejestrując element DbContext jako przejściowy (przy użyciu przeciążenia elementu AddDbContext, który przyjmuje parametr ServiceLifetime).

Więcej informacji