Životnost, konfigurace a inicializace instancí DbContext

Tento článek se věnuje základním vzorům inicializace a konfigurace instance DbContext.

Životnost instance DbContext

Životnost instance DbContext začíná jejím vytvoření a končí jejím odstraněním. Instance DbContext je navržená tak, aby se používala pro jednu jednotku práce. To znamená, že životnost instance DbContext je obvykle velmi krátká.

Tip

Když použijeme slova Martina Fowlera z odkazu výše, „jednotka práce sleduje vše, co během obchodní transakce děláte a co by mohlo ovlivnit databázi. Po dokončení zjistí, co je potřeba udělat, aby se databáze v důsledku vaší práce změnila.“

Typická jednotka práce při použití nástroje Entity Framework Core (EF Core) zahrnuje:

Důležité

  • Je velmi důležité instanci DbContext po použití odstranit. Tím zajistíte, že všechny nespravované prostředky budou uvolněny a že nebudou registrovány žádné události nebo jiná zavěšení, čímž se zabrání nevracení paměti, k němuž by došlo při pokračujícím odkazování na instanci.
  • Instance DbContext není bezpečná pro přístup z více vláken. Nesdílejte kontexty mezi vlákny. Než budete pokračovat v práci s instancí kontextu, nezapomeňte počkat na všechna asynchronní volání.
  • Vyvolání InvalidOperationException kódem EF Core může způsobit neopravitelný stav kontextu. Tyto výjimky značí chybu programu a nejsou určeny k zotavení.

DbContext v injektáži závislostí pro ASP.NET Core

V mnoha webových aplikacích odpovídá každý požadavek HTTP jedné jednotce práce. Díky tomu je svázání životnosti kontextu s požadavkem dobrou výchozí volbou pro webové aplikace.

Aplikace ASP.NET Core se konfigurují pomocí injektáže závislostí. EF Core lze do této konfigurace přidat pomocí AddDbContextv metodě ConfigureServices souboru Startup.cs. Příklad:

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

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

Tento příklad zaregistruje podtřídu DbContext s názvemApplicationDbContext jako službu s vymezeným oborem v poskytovateli služby aplikace ASP.NET Core (kontejner injektáží závislostí). Kontext je nakonfigurován tak, aby používal poskytovatele databáze SQL Server a přečte připojovací řetězec z konfigurace ASP.NET Core. Obvykle nezáleží na tom, kde v ConfigureServices se volání AddDbContext provádí.

Třída ApplicationDbContext musí vystavit veřejný konstruktor s parametrem DbContextOptions<ApplicationDbContext> . Tímto způsobem se předá konfigurace kontextu z AddDbContext do instance DbContext. Příklad:

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

ApplicationDbContext pak lze použít v kontrolerech ASP.NET Core nebo jiných službách prostřednictvím injektáže konstruktoru. Příklad:

public class MyController
{
    private readonly ApplicationDbContext _context;

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

Výsledkem je instance ApplicationDbContext vytvořená pro každou žádost a předaná kontroleru, kde provádí jednotku práce a po ukončení žádosti se odstraní.

Podrobnosti o možnostech konfigurace najdete dále v tomto článku. Informace o konfiguraci a injektáži závislostí v ASP.NET Core najdete také v tématech věnovaných spuštění aplikace v ASP.NET Core a injektáži závislostí v ASP.NET Core.

Jednoduchá inicializace instance DbContext s parametrem „new“

Instance DbContext lze vytvářet způsobem obvyklým pro prostředí .NET, například s new v jazyce C#. Konfiguraci lze provést přepsáním metody OnConfiguring nebo předáním možností konstruktoru. Příklad:

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

Tento vzorec také usnadňuje předávání konfigurací, jako je připojovací řetězec, prostřednictvím konstruktoru DbContext . Příklad:

public class ApplicationDbContext : DbContext
{
    private readonly string _connectionString;

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

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

Alternativně lze k vytvoření objektu DbContextOptions, který se následně předá konstruktoru DbContext, použít DbContextOptionsBuilder. Díky tomu lze instanci DbContext nakonfigurovanou pro injektáž závislostí sestavit také explicitně. Například při použití ApplicationDbContext definovaného pro webové aplikace ASP.NET Core výše:

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

Je možné vytvořit DbContextOptions a explicitně volat konstruktor:

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

using var context = new ApplicationDbContext(contextOptions);

Použití objektu pro vytváření DbContext (např. pro Blazor)

Některé typy aplikací (např. ASP.NET Core Blazor) využívají injektáž závislostí, ale nevytváří obor služby, který odpovídá požadované životnosti DbContext. I tam, kde se obor služby s životností shoduje, může aplikace v daném oboru provádět více jednotek práce. Může se například jednat o několik jednotek práce v rámci jednoho požadavku HTTP.

V těchto případech lze pomocí AddDbContextFactory zaregistrovat objekt pro vytváření instancí DbContext. Příklad:

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

Třída ApplicationDbContext musí vystavit veřejný konstruktor s parametrem DbContextOptions<ApplicationDbContext> . Jedná se o stejný vzor jako ten, který je uvedený v části věnované ASP.NET Core výše.

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

Objekt pro vytváření DbContextFactory lze následně použít v jiných službách prostřednictvím injektáže konstruktoru. Příklad:

private readonly IDbContextFactory<ApplicationDbContext> _contextFactory;

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

Injektovaný objekt pro vytváření lze následně použít k vytváření instancí DbContext v kódu služby. Příklad:

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

Všimněte si, že instance DbContext vytvořené tímto způsobem nejsou spravovány poskytovatelem služby aplikace, a proto musí být odstraněny aplikací.

Další informace o používání EF Core s aplikací Blazor najdete v tématu ASP.NET Core Blazor Server s Entity Framework Core.

DbContextOptions

Výchozím bodem pro všechny konfigurace instance DbContext je DbContextOptionsBuilder. Tohoto tvůrce můžete získat třemi způsoby:

  • V AddDbContext a souvisejících metodách
  • V OnConfiguring
  • Explicitní sestavení pomocí new

Příklady jsou uvedeny v předchozích částech. Konfiguraci lze použít bez ohledu na to, jak byl tvůrce sestaven. Kromě toho se vždy volá OnConfiguring, ať byl kontext vytvořen jakkoliv. To znamená, že OnConfiguring lze použít k provedení další konfigurace i tehdy, když se používá AddDbContext.

Konfigurace poskytovatele databáze

Každá instance DbContext musí být nakonfigurovaná tak, aby používala výhradně jednoho poskytovatele databáze. (Při různých poskytovatelích databáze lze použít různé instance podtypu DbContext, ale jedna instance vyžaduje pouze jednoho poskytovatele.) Poskytovatel databáze je nakonfigurován pomocí specifického volání Use*. Pokud chcete například použít poskytovatele databáze SQL Server:

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

Metody Use* představují rozšiřující metody implementované poskytovatelem databáze. To znamená, že ještě před použitím rozšiřující metody je nutné nainstalovat balíček NuGet poskytovatele databáze.

Tip

Poskytovatelé databází EF Core využívají rozšiřující metody velmi často. Pokud kompilátor signalizuje, že metodu nelze najít, ujistěte se, že je balíček NuGet poskytovatele nainstalovaný a že máte v kódu using Microsoft.EntityFrameworkCore;.

V následující tabulce najdete příklady běžných poskytovatelů databází.

Databázový systém Příklad konfigurace Balíček NuGet
SQL Server nebo Azure SQL .UseSqlServer(connectionString) Microsoft.EntityFrameworkCore.SqlServer
Azure Cosmos DB .UseCosmos(connectionString, databaseName) Microsoft.EntityFrameworkCore.Cosmos
SQLite .UseSqlite(connectionString) Microsoft.EntityFrameworkCore.Sqlite
Databáze EF Core v paměti .UseInMemoryDatabase(databaseName) Microsoft.EntityFrameworkCore.InMemory
PostgreSQL* .UseNpgsql(connectionString) Npgsql.EntityFrameworkCore.PostgreSQL
MySQL/MariaDB* .UseMySql(connectionString) Pomelo.EntityFrameworkCore.MySql
Věštírna* .UseOracle(connectionString) Oracle.EntityFrameworkCore

*Tyto poskytovatele databází nedodává společnost Microsoft. Další informace o poskytovatelích databází najdete v tématu Poskytovatelé databází.

Upozorňující

Databáze EF Core v paměti není určená k produkčnímu použití. Kromě toho nemusí být nejlepší volbou ani pro testování. Další informace najdete v tématu Testování kódu, který používá EF Core.

Další informace o používání připojovacích řetězců s EF Core najdete v tématu Připojovací řetězce.

Volitelná konfigurace specifická pro poskytovatele databáze se provádí v doplňkovém tvůrci určeném pro daného poskytovatele. Příkladem je použití EnableRetryOnFailure ke konfiguraci opakování za účelem zajištění odolnosti připojení při připojování k Azure SQL:

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

Tip

Pro SQL Server a Azure SQL se používá stejný poskytovatel databáze. Při připojování k SQL Azure se ale doporučuje použít odolnost připojení.

Další informace o konfiguracích specifických pro konkrétní poskytovatele najdete v tématu Poskytovatelé databází.

Další konfigurace instance DbContext

Další konfiguraci DbContext je možné zřetězit před nebo za (výsledek bude stejný) volání Use*. Pokud například chcete zapnout protokolování citlivých dat:

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

V následující tabulce najdete příklady běžných metod volaných na DbContextOptionsBuilder.

Metoda DbContextOptionsBuilder Jak funguje Další informace
UseQueryTrackingBehavior Nastaví výchozí chování sledování dotazů Chování sledování dotazů
LogTo Jednoduchý způsob, jak získat protokoly EF Core Protokolování, události a diagnostika
UseLoggerFactory Zaregistruje objekt pro vytváření Microsoft.Extensions.Logging Protokolování, události a diagnostika
EnableSensitiveDataLogging Obsahuje data aplikací ve výjimkách a protokolování Protokolování, události a diagnostika
EnableDetailedErrors Podrobnější chyby dotazů (na úkor výkonu) Protokolování, události a diagnostika
ConfigureWarnings Ignorování nebo vyvolání upozornění a dalších událostí Protokolování, události a diagnostika
AddInterceptors Zaregistruje zachycovače EF Core Protokolování, události a diagnostika
UseLazyLoadingProxies Použití dynamických proxy serverů pro opožděné načítání Opožděné načítání
UseChangeTrackingProxies Použití dynamických proxy serverů pro sledování změn Připravujeme...

Poznámka:

UseLazyLoadingProxies a UseChangeTrackingProxies jsou rozšiřující metody z balíčku NuGet Microsoft.EntityFrameworkCore.Proxies. Tento druh volání „.UseSomething()“ představuje doporučený způsob, jak konfigurovat a/nebo používat rozšíření EF Core obsažená v jiných balíčcích.

DbContextOptions versus DbContextOptions<TContext>

Většina DbContext podtříd, které přijímají, DbContextOptions by měla používat obecnou DbContextOptions<TContext> variantu. Příklad:

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

Tím zajistíte, že se z injektáže závislostí přeloží správné možnosti pro konkrétní podtyp DbContext, a to i v případě, že je zaregistrováno více podtypů DbContext.

Tip

DbContext není nutné zapečetit, ale zapečetění je osvědčeným postupem pro třídy, které nejsou navrženy tak, aby se z nich dědilo.

Pokud je však podtyp DbContext sám o sobě navržen tak, aby se z něj dědilo, měl by vystavit chráněný konstruktor s obecným DbContextOptions. Příklad:

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

To umožňuje více konkrétním podtřídám volat tento základní konstruktor pomocí svých různých obecných instancí DbContextOptions<TContext>. Příklad:

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

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

Všimněte si, že se jedná o úplně stejný vzor jako při přímém dědění z DbContext. To znamená, že samotný kostruktor DbContext tak přijímá neobecné DbContextOptions.

Podtřída DbContext určená k vytvoření instance i dědění by měla vystavit obě formy konstruktorů. Příklad:

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

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

Konfigurace DbContext v době návrhu

Nástroje pro návrh EF Core, například pro migraci EF Core, musí být schopné nacházet a vytvářet funkční instanci typu DbContext, aby bylo možné shromažďovat podrobnosti o typech entit aplikace a jejich mapování do schématu databáze. Tento proces může být automatický za předpokladu, že nástroj dokáže snadno vytvořit instanci DbContext se stejnou konfigurací jako při konfiguraci za běhu.

I když mohou za běhu fungovat všechny modely, které instanci DbContext poskytují všechny potřebné informace o konfiguraci, nástroje vyžadující použití DbContext v době návrhu mohou pracovat pouze s omezeným počtem vzorů. Ty jsou podrobně popsány v tématu věnovaném vytváření kontextu v době návrhu.

Předcházení problémům s dělením na vlákna u instance DbContext

Entity Framework Core nepodporuje spouštění více paralelních operací ve stejné instanci DbContext. To zahrnuje paralelní spouštění asynchronních dotazů i jakékoli explicitní souběžné používání z více vláken. Proto vždy await asynchronní volání okamžitě nebo použijte samostatné DbContext instance pro operace, které se spouští paralelně.

Když EF Core detekuje pokus o souběžné použití instance DbContext, zobrazí se InvalidOperationException se zprávou podobnou této:

Druhá operace byla tomto kontextu spuštěna před dokončením předchozí operace. To je obvykle způsobeno tím, že jednu instanci DbContext využívají různá vlákna, ale u členů instance není zaručena bezpečnost pro přístup z více vláken.

Není-li souběžný přístup detekován, může způsobit nedefinované chování, chybová ukončení aplikace a poškození dat.

Toto jsou běžné chyby, které mohou způsobit neúmyslný souběžný přístup k jedné instanci DbContext:

Úskalí asynchronních operací

Asynchronní metody umožňují nástroji EF Core inicializovat operace, které přistupují neblokujícím způsobem. Pokud ale volající nečeká na dokončení jedné z těchto metod a pokračuje v provádění dalších operací v instanci DbContext, může se DbContext poškodit (a velmi pravděpodobné se tak stane).

Vždy vyčkejte na dokončení asynchronních metod EF Core.

Implicitní sdílení instancí DbContext prostřednictvím injektáže závislostí

Metoda rozšíření AddDbContext ve výchozím nastavení registruje typy DbContext s vymezenou životností.

Díky tomu ve většině aplikací ASP.NET Core nedochází k problémům se souběžným přístupem, protože v daném okamžiku provádí požadavek klienta pouze jedno vlákno a protože každý požadavek získá samostatný rozsah injektáže závislostí (a tím i samostatnou instanci DbContext). U modelu hostování aplikace Blazor Server se pro údržbu okruhu uživatele Blazor používá jeden logický požadavek, a proto je pro každý okruh uživatele k dispozici pouze jedna vymezená instance DbContext, pokud se používá výchozí rozsah injektáže.

U jakéhokoli kódu, který explicitně spouští více vláken paralelně, by mělo být zajištěno, že k instancím DbContext se nikdy nebude přistupovat souběžně.

Při použití injektáže závislostí toho lze dosáhnout buď registrací kontextu jako kontextu s vymezeným oborem a vytvořením oborů (pomocí IServiceScopeFactory) pro každé vlákno, nebo registrací instance DbContext jako přechodné instance (pomocí přetížení AddDbContext, které převezme parametr ServiceLifetime).

Další informace