Ž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:
- Vytvoření instance
DbContext
- Sledování instancí entit podle kontextu. Entity se sledují podle
- Změny se provádějí u sledovaných entit tak, jak je to potřeba pro implementaci obchodního pravidla
- Probíhá volání SaveChanges nebo SaveChangesAsync. EF Core detekuje provedené změny a zapíše je do databáze.
- Instance
DbContext
je odstraněna
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
- Další informace o injektáži závislostí najdete v tématu Injektáž závislostí.
- Další informace najdete v tématu Testování.