DbContext-Lebensdauer, -Konfiguration und -Initialisierung
Dieser Artikel zeigt grundlegende Muster für die Initialisierung und Konfiguration einer DbContext-Instanz.
Warnung
In diesem Artikel wird eine lokale Datenbank verwendet, für die keine Authentifizierung des Benutzers erforderlich ist. Produktions-Apps sollten den sichersten verfügbaren Ablauf für die Authentifizierung verwenden. Weitere Informationen zur Authentifizierung für bereitgestellte Test- und Produktions-Apps finden Sie unter Sichere Authentifizierungsflows.
Die DbContext-Lebensdauer
Die Lebensdauer eines DbContext
beginnt mit dem Erstellen der Instanz und endet, wenn die Instanz verworfen wird. Eine DbContext
-Instanz ist für die Verwendung in einer einzelnen Arbeitseinheit konzipiert. Dies bedeutet, dass die Lebensdauer einer DbContext
-Instanz in der Regel sehr kurz ist.
Tipp
Um Martin Fowler aus dem Link oben zu zitieren: „Eine Arbeitseinheit verfolgt alle Aufgaben nach, die Sie während einer Geschäftstransaktion ausführen, die sich auf die Datenbank auswirken können. Wenn Sie fertig sind, findet sie alles heraus, was getan werden muss, um die Datenbank als Ergebnis Ihrer Arbeit zu verändern“.
Eine typische Arbeitseinheit bei Verwendung von Entity Framework Core (EF Core) umfasst Folgendes:
- Erstellen einer
DbContext
-Instanz. - Nachverfolgen von Entitätsinstanzen durch den Kontext. Entitäten werden folgendermaßen nachverfolgt:
- An den nachverfolgten Entitäten werden Änderungen vorgenommen, die für die Implementierung der Geschäftsregel erforderlich sind.
- SaveChanges oder SaveChangesAsync wird aufgerufen. EF Core erkennt die vorgenommenen Änderungen und schreibt sie in die Datenbank.
- Die
DbContext
Instanz wird verworfen.
Wichtig
- Es ist wichtig, die DbContext Nachverwendung zu verwerfen. Dadurch wird folgendes sichergestellt:
- Nicht verwaltete Ressourcen werden freigegeben.
- Ereignisse oder andere Hooks werden nicht registriert. Durch das Aufheben der Registrierung werden Speicherverluste verhindert, wenn auf die Instanz verwiesen wird.
- DbContext ist nicht threadsicher. Verwenden Sie keine Kontexte zwischen Threads. Stellen Sie sicher, dass await für alle asynchronen Aufrufe verwendet wird, bevor Sie die Kontextinstanz weiterhin verwenden.
- Eine InvalidOperationException, die von EF Core-Code ausgelöst wird, kann den Kontext in einen nicht wiederherstellbaren Zustand versetzen. Solche Ausnahmen weisen auf einen Programmfehler hin und sind nicht für Wiederherstellung konzipiert.
DbContext in Abhängigkeitsinjektion für ASP.NET Core
In vielen Webanwendungen entspricht jede HTTP-Anforderung einer einzelnen Arbeitseinheit. Dies bewirkt, dass das Anbinden der Kontextlebensdauer an die Anforderung für Webanwendungen eine gute Standardeinstellung ist.
ASP.NET Core-Anwendungen werden mithilfe von Abhängigkeitsinjektion konfiguriert. EF Core kann dieser Konfiguration mithilfe von AddDbContext Program.cs
Zum Beispiel:
var connectionString =
builder.Configuration.GetConnectionString("DefaultConnection")
?? throw new InvalidOperationException("Connection string"
+ "'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
Der vorangehende Code registriert ApplicationDbContext
eine Unterklasse von DbContext
, als bereichsbezogener Dienst im ASP.NET Core-App-Dienstanbieter. Der Dienstanbieter wird auch als Container zum Einfügen von Abhängigkeiten bezeichnet. Der Kontext ist für die Verwendung des SQL Server-Datenbankanbieters konfiguriert und liest die Verbindungszeichenfolge aus ASP.NET Core-Konfiguration vor.
Die ApplicationDbContext
-Klasse muss einen öffentlichen Konstruktor mit einem Parameter DbContextOptions<ApplicationDbContext>
bereitstellen. Auf diese Weise wird die Kontextkonfiguration aus AddDbContext
an DbContext
übergeben. Zum Beispiel:
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
ApplicationDbContext
kann in ASP.NET Core-Controllern oder anderen Diensten über die Konstruktoreinfügung verwendet werden:
public class MyController
{
private readonly ApplicationDbContext _context;
public MyController(ApplicationDbContext context)
{
_context = context;
}
}
Das Endergebnis ist eine ApplicationDbContext
-Instanz, die für jede Anforderung erstellt und an den Controller zum Ausführen einer Arbeitseinheit übergeben wird, bevor sie verworfen wird, wenn die Anforderung beendet wird.
Weitere Informationen zu den Konfigurationsoptionen finden Sie weiter unten in diesem Artikel. Weitere Informationen finden Sie unter Abhängigkeitsinjektion in ASP.NET Core .
Grundlegende DbContext-Initialisierung mit 'new'
DbContext
Instanzen können in new
C# erstellt werden. Die Konfiguration kann durch Überschreiben der OnConfiguring
-Methode oder durch Übergeben von Optionen an den Konstruktor ausgeführt werden. Beispiel:
public class ApplicationDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(
@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");
}
}
Mit diesem Muster ist es auch einfach, Konfiguration wie etwa eine Verbindungszeichenfolge über den DbContext
-Konstruktor zu übergeben. Beispiel:
public class ApplicationDbContext : DbContext
{
private readonly string _connectionString;
public ApplicationDbContext(string connectionString)
{
_connectionString = connectionString;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(_connectionString);
}
}
Alternativ kann DbContextOptionsBuilder
verwendet werden, um ein DbContextOptions
-Objekt zu erstellen, das dann an den DbContext
-Konstruktor übergeben wird. Dadurch kann ein DbContext
, der für Abhängigkeitsinjektion konfiguriert ist, ebenfalls explizit erstellt werden. Wenn Sie z. B. ApplicationDbContext
verwenden, der für ASP.NET Core Web-Apps oben definiert ist:
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
DbContextOptions
kann erstellt werden, und der Konstruktor kann explizit aufgerufen werden:
var contextOptions = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0")
.Options;
using var context = new ApplicationDbContext(contextOptions);
Verwenden einer DbContext-Factory
Einige Anwendungstypen (z. B. ASP.NET Core Blazor) verwenden Abhängigkeitsinjektion, erstellen aber keinen Dienstbereich, der der gewünschten DbContext
-Lebensdauer entspricht. Auch wenn eine solche Ausrichtung vorhanden ist, muss die Anwendung möglicherweise mehrere Arbeitseinheiten innerhalb dieses Bereichs ausführen. Beispielsweise mehrere Arbeitseinheiten innerhalb einer einzelnen HTTP-Anforderung.
In diesen Fällen kann AddDbContextFactory zum Registrieren einer Factory zum Erstellen von DbContext
-Instanzen verwendet werden. Beispiel:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextFactory<ApplicationDbContext>(
options => options.UseSqlServer(
@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0"));
}
Die ApplicationDbContext
-Klasse muss einen öffentlichen Konstruktor mit einem Parameter DbContextOptions<ApplicationDbContext>
bereitstellen. Dabei handelt es sich um dasselbe Muster, das im Abschnitt „Traditionelles ASP.NET Core“ weiter oben verwendet wird.
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
Die DbContextFactory
-Factory kann dann mithilfe von Konstruktorinjektion in anderen Diensten verwendet werden. Beispiel:
private readonly IDbContextFactory<ApplicationDbContext> _contextFactory;
public MyController(IDbContextFactory<ApplicationDbContext> contextFactory)
{
_contextFactory = contextFactory;
}
Die injizierte Factory kann dann verwendet werden, um DbContext-Instanzen im Dienstcode zu erstellen. Beispiel:
public void DoSomething()
{
using (var context = _contextFactory.CreateDbContext())
{
// ...
}
}
Beachten Sie, dass die DbContext
-Instanzen, die auf diese Weise erstellt werden, nicht vom Dienstanbieter der Anwendung verwaltet werden, weshalb diese von der Anwendung gelöscht werden müssen.
Weitere Informationen zur Verwendung von EF Core mit Blazor finden Sie unter ASP.NET Core Blazor Server mit Entity Framework Core.
DbContextOptions
Der Ausgangspunkt für die gesamte DbContext
-Konfiguration ist DbContextOptionsBuilder. Es gibt drei Möglichkeiten, diesen Generator abzurufen:
- In
AddDbContext
und verwandte Methoden - In
OnConfiguring
- Explizit generiert mit
new
Beispiele für jede dieser Möglichkeiten werden in den vorangehenden Abschnitten aufgeführt. Dieselbe Konfiguration kann unabhängig davon angewendet werden, wie der Generator abgerufen wurde. Außerdem wird OnConfiguring
immer unabhängig davon aufgerufen, wie der Kontext erstellt wird. Dies bedeutet, dass OnConfiguring
selbst dann verwendet werden kann, um zusätzliche Konfigurationen auszuführen, wenn AddDbContext
verwendet wird.
Konfigurieren des Datenbankanbieters
Jede DbContext
-Instanz muss so konfiguriert werden, dass sie nur einen Datenbankanbieter verwendet. (Verschiedene Instanzen eines DbContext
-Untertyps können mit unterschiedlichen Datenbankanbietern verwendet werden, eine einzelne Instanz darf jedoch nur einen Datenbankanbieter verwenden.) Ein Datenbankanbieter wird mithilfe eines bestimmten Use*
-Aufrufs konfiguriert. Wenn beispielsweise der SQL Server-Datenbankanbieter verwendet werden soll:
public class ApplicationDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(
@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");
}
}
Diese Use*
-Methoden sind Erweiterungsmethoden, die vom Datenbankanbieter implementiert werden. Dies bedeutet, dass das NuGet-Paket des Datenbankanbieters installiert werden muss, bevor die Erweiterungsmethode verwendet werden kann.
Tipp
EF Core-Datenbankanbieter nutzen Erweiterungsmethoden ausgiebig. Wenn der Compiler angibt, dass eine Methode nicht gefunden werden kann, stellen Sie sicher, dass das NuGet-Paket des Anbieters installiert ist und dass Sie using Microsoft.EntityFrameworkCore;
in Ihrem Code verwenden.
Die folgende Tabelle enthält Beispiele für gängige Datenbankanbieter.
Datenbanksystem | Beispielkonfiguration | NuGet-Paket |
---|---|---|
SQL Server oder Azure SQL | .UseSqlServer(connectionString) |
Microsoft.EntityFrameworkCore.SqlServer |
Azure Cosmos DB | .UseCosmos(connectionString, databaseName) |
Microsoft.EntityFrameworkCore.Cosmos |
SQLite | .UseSqlite(connectionString) |
Microsoft.EntityFrameworkCore.Sqlite |
EF Core-In-Memory-Datenbank | .UseInMemoryDatabase(databaseName) |
Microsoft.EntityFrameworkCore.InMemory |
PostgreSQL* | .UseNpgsql(connectionString) |
Npgsql.EntityFrameworkCore.PostgreSQL |
MySQL/MariaDB* | .UseMySql(connectionString) |
Pomelo.EntityFrameworkCore.MySql |
Oracle* | .UseOracle(connectionString) |
Oracle.EntityFrameworkCore |
*Diese Datenbankanbieter werden nicht von Microsoft ausgeliefert. Weitere Informationen zu Datenbankanbietern finden Sie unter Datenbankanbieter.
Warnung
Die In-Memory-Datenbank von EF Core ist nicht für die Verwendung in der Produktion vorgesehen. Außerdem ist sie möglicherweise auch nicht die beste Wahl für Tests. Weitere Informationen finden Sie unter Testen von Code, der EF Core verwendet.
Weitere Informationen zur Verwendung von Verbindungszeichenfolgen mit EF Core finden Sie unter Verbindungszeichenfolgen.
Eine für den Datenbankanbieter spezifische optionale Konfiguration wird in einem zusätzlichen anbieterspezifischen Generator ausgeführt. Verwenden Sie beispielsweise EnableRetryOnFailure, um Wiederholungsversuche für Verbindungsresilienz beim Herstellen einer Verbindung mit Azure SQL zu konfigurieren:
public class ApplicationDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(
@"Server=(localdb)\mssqllocaldb;Database=Test",
providerOptions => { providerOptions.EnableRetryOnFailure(); });
}
}
Tipp
Der gleiche Datenbankanbieter wird für SQL Server und Azure SQL verwendet. Es wird jedoch empfohlen, beim Herstellen einer Verbindung mit SQL Azure Verbindungsresilienz zu verwenden.
Weitere Informationen zur anbieterspezifischen Konfiguration finden Sie unter Datenbankanbieter.
Weitere DbContext-Konfiguration
Weitere DbContext
-Konfiguration kann vor oder nach dem Aufruf von Use*
verkettet werden (dies macht keinen Unterschied). So aktivieren Sie z. B. Protokollierung sensibler Daten:
public class ApplicationDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.EnableSensitiveDataLogging()
.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");
}
}
Die folgende Tabelle enthält Beispiele für gängige Methoden, die für DbContextOptionsBuilder
aufgerufen werden.
DbContextOptionsBuilder-Methode | Funktion | Weitere Informationen |
---|---|---|
UseQueryTrackingBehavior | Legt das Standardverhalten der Nachverfolgung für Abfragen fest. | Verhalten der Abfragenachverfolgung |
LogTo | Eine einfache Möglichkeit, EF Core-Protokolle zu erhalten | Protokollierung, Ereignisse und Diagnose |
UseLoggerFactory | Registriert eine Microsoft.Extensions.Logging -Factory. |
Protokollierung, Ereignisse und Diagnose |
EnableSensitiveDataLogging | Schließt Anwendungsdaten in Ausnahmen und Protokollierung ein. | Protokollierung, Ereignisse und Diagnose |
EnableDetailedErrors | Ausführlichere Abfragefehler (auf Kosten der Leistung). | Protokollierung, Ereignisse und Diagnose |
ConfigureWarnings | Ignoriert Warnungen und andere Ereignisse oder löst diese aus. | Protokollierung, Ereignisse und Diagnose |
AddInterceptors | Registriert EF Core-Interceptors. | Protokollierung, Ereignisse und Diagnose |
UseLazyLoadingProxies | Verwendet dynamischer Proxys für verzögertes Laden. | Verzögertes Laden |
UseChangeTrackingProxies | Verwendet dynamische Proxys für Änderungsnachverfolgung. | Bald verfügbar... |
Hinweis
UseLazyLoadingProxies und UseChangeTrackingProxies sind Erweiterungsmethoden aus dem NuGet-Paket Microsoft.EntityFrameworkCore.Proxies. Diese Art von „.UseSomething()“-Aufruf ist die empfohlene Methode zur Konfiguration und/oder Verwendung von EF Core-Erweiterungen, die in anderen Paketen enthalten sind.
DbContextOptions
im Vergleich mit DbContextOptions<TContext>
Die meisten DbContext
-Unterklassen, die DbContextOptions
akzeptieren, sollten die generische DbContextOptions<TContext>
-Variation verwenden. Beispiel:
public sealed class SealedApplicationDbContext : DbContext
{
public SealedApplicationDbContext(DbContextOptions<SealedApplicationDbContext> contextOptions)
: base(contextOptions)
{
}
}
Dadurch wird sichergestellt, dass die richtigen Optionen für den spezifischen DbContext
-Untertyp aus der Abhängigkeitsinjektion aufgelöst werden, auch wenn mehrere DbContext
-Untertypen registriert sind.
Tipp
Ihr DbContext muss nicht versiegelt sein, aber das Versiegeln ist eine bewährte Vorgehensweise für Klassen, die nicht für Vererbung entworfen wurden.
Wenn jedoch vom DbContext
-Untertyp selbst geerbt werden soll, sollte ein geschützter Konstruktor bereitgestellt werden, der generische DbContextOptions
annimmt. Beispiel:
public abstract class ApplicationDbContextBase : DbContext
{
protected ApplicationDbContextBase(DbContextOptions contextOptions)
: base(contextOptions)
{
}
}
Dies ermöglicht es mehreren konkreten Unterklassen, diesen Basiskonstruktor mithilfe ihrer verschiedenen generischen DbContextOptions<TContext>
-Instanzen aufzurufen. Beispiel:
public sealed class ApplicationDbContext1 : ApplicationDbContextBase
{
public ApplicationDbContext1(DbContextOptions<ApplicationDbContext1> contextOptions)
: base(contextOptions)
{
}
}
public sealed class ApplicationDbContext2 : ApplicationDbContextBase
{
public ApplicationDbContext2(DbContextOptions<ApplicationDbContext2> contextOptions)
: base(contextOptions)
{
}
}
Beachten Sie, dass dies genau das gleiche Muster wie beim direkten Erben von DbContext
ist. Das heißt, der DbContext
-Konstruktor selbst akzeptiert aus diesem Grund nicht generische DbContextOptions
.
Eine DbContext
-Unterklasse, die instanziiert und von der geerbt werden soll, sollte beide Formen des Konstruktors verfügbar machen. Beispiel:
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> contextOptions)
: base(contextOptions)
{
}
protected ApplicationDbContext(DbContextOptions contextOptions)
: base(contextOptions)
{
}
}
DbContext-Konfiguration zur Entwurfszeit
EF Core-Entwurfszeittools (z. B. für EF Core-Migrationen) müssen in der Lage sein, eine funktionierende Instanz eines DbContext
-Typs zu ermitteln und zu erstellen, um Details zu den Entitätstypen der Anwendung und deren Zuordnung zu einem Datenbankschema zu erfassen. Dieser Prozess kann automatisch ausgeführt werden, solange das Tool die DbContext
-Instanz problemlos so erstellen kann, dass sie ähnlich konfiguriert wird, wie sie zur Laufzeit konfiguriert würde.
Obwohl jedes Muster, das die erforderlichen Konfigurationsinformationen für die DbContext
-Instanz bereitstellt, zur Laufzeit funktionieren kann, können Tools, die die Verwendung einer DbContext
-Instanz zur Entwurfszeit erfordern, nur mit einer begrenzten Anzahl von Mustern funktionieren. Diese werden unter Kontexterstellung zur Entwurfszeit ausführlicher behandelt.
Vermeiden von DbContext-Threadingproblemen
Entity Framework Core unterstützt nicht die Ausführung mehrerer paralleler Vorgänge, die für dieselbe DbContext
-Instanz ausgeführt werden. Dies schließt die parallele Ausführung von asynchronen Abfragen und jede explizite gleichzeitige Verwendung aus mehreren Threads ein. Verwenden Sie daher immer sofort asynchrone await
-Aufrufe, oder verwenden Sie separate DbContext
-Instanzen für Vorgänge, die parallel ausgeführt werden.
Wenn EF Core den Versuch erkennt, eine DbContext
-Instanz parallel zu verwenden, wird eine InvalidOperationException
mit einer Meldung wie der folgenden angezeigt:
Ein zweiter Vorgang wurde für diesen Kontext gestartet, bevor ein vorheriger Vorgang abgeschlossen wurde. Dies wird in der Regel durch verschiedene Threads verursacht, die dieselbe Instanz von DbContext verwenden, aber es ist nicht garantiert, dass Instanzmember threadsicher sind.
Wenn gleichzeitiger Zugriff nicht erkannt wird, kann dies zu nicht definiertem Verhalten, Anwendungsabstürzen und Datenbeschädigung führen.
Es gibt häufige Fehler, die versehentlich gleichzeitigen Zugriff auf dieselbe DbContext
-Instanz verursachen können:
Fehler bei asynchronen Vorgängen
Asynchrone Methoden ermöglichen EF Core das Initiieren von Vorgängen, die auf nicht blockierende Weise auf die Datenbank zugreifen. Wenn ein Aufrufer jedoch nicht auf den Abschluss einer dieser Methoden wartet und andere Vorgänge für DbContext
ausführt, kann der Status von DbContext
beschädigt sein (und ist es sehr wahrscheinlich auch).
Warten Sie immer sofort auf asynchrone EF Core-Methoden.
Implizites Freigeben von DbContext-Instanzen über Abhängigkeitsinjektion
Die Erweiterungsmethode AddDbContext
registriert DbContext
-Typen standardmäßig mit einer begrenzten Lebensdauer.
Dies vermeidet in den meisten ASP.NET Core-Anwendungen Probleme durch gleichzeitigen Zugriff, weil es nur einen Thread gibt, der jede Clientanforderung zu einer bestimmten Zeit ausführt, und weil jede Anforderung einen separaten Abhängigkeitsinjektionsbereich (und damit eine separate DbContext
-Instanz) erhält. Für das Blazor Server-Hostingmodell wird eine logische Anforderung zum Verwalten der Blazor-Benutzerverbindung verwendet. Daher ist nur eine bereichsbezogene DbContext-Instanz pro Benutzerverbindung verfügbar, wenn der standardmäßige Injektionsbereich verwendet wird.
Jeder Code, der explizit mehrere Threads parallel ausführt, sollte sicherstellen, dass auf DbContext
-Instanzen niemals gleichzeitig zugegriffen wird.
Mithilfe von Abhängigkeitsinjektion kann dies erreicht werden, indem der Kontext als bereichsbezogen registriert wird und Bereiche (mit IServiceScopeFactory
) für jeden Thread erstellt werden, oder indem DbContext
als vorübergehender Wert registriert wird (mithilfe der Überladung von AddDbContext
, die einen ServiceLifetime
-Parameter annimmt).
Weitere Informationen
- Unter Abhängigkeitsinjektion finden Sie weitere Informationen zur Abhängigkeitsinjektion.
- Weitere Informationen finden Sie unter Tests.