DbContext Livslängd, konfiguration och initiering

Den här artikeln visar grundläggande mönster för initiering och konfiguration av en DbContext instans.

Varning

Den här artikeln använder en lokal databas som inte kräver att användaren autentiseras. Produktionsappar bör använda det säkraste tillgängliga autentiseringsflödet. Mer information om autentisering för distribuerade test- och produktionsappar finns i Säkra autentiseringsflöden.

DbContext-livslängden

Livslängden för en DbContext börjar när instansen skapas och slutar när instansen tas bort. En DbContext instans är utformad för att användas för en endaarbetsenhet. Det innebär att livslängden för en DbContext instans vanligtvis är mycket kort.

Tips/Råd

För att citera Martin Fowler från länken ovan, "En arbetsenhet håller reda på allt du gör under en affärstransaktion som kan påverka databasen. När du är klar räknar den ut allt som behöver göras för att ändra databasen som ett resultat av ditt arbete."

En typisk arbetsenhet när du använder Entity Framework Core (EF Core) omfattar:

Viktigt!

  • Det är viktigt att kassera efter DbContext användning. Detta säkerställer något:
    • Ohanterade resurser frigörs.
    • Händelser eller andra kopplingar avregistreras. Avregistrering förhindrar minnesläckor när instansen förblir refererad.
  • DbContext är Inte trådsäkert. Dela inte kontexter mellan trådar. Vänta på alla asynkrona anrop innan du fortsätter att använda kontextinstansen.
  • En InvalidOperationException som utlöses av EF Core-kod kan försätta kontexten i ett oåterkalleligt tillstånd. Sådana undantag indikerar ett programfel och är inte utformade för att återställas från.

DbContext med beroendeinjektion i ASP.NET Core

I många webbprogram motsvarar varje HTTP-begäran en enda arbetsenhet. Detta gör att bindningen av kontextens livslängd till begäran är en bra standard för webbprogram.

ASP.NET Core-program konfigureras med hjälp av beroendeinmatning. EF Core kan läggas till i den här konfigurationen med hjälp av AddDbContext i Program.cs. Till exempel:

var connectionString =
    builder.Configuration.GetConnectionString("DefaultConnection")
        ?? throw new InvalidOperationException("Connection string"
        + "'DefaultConnection' not found.");

builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));

Föregående kod registrerar ApplicationDbContext, en underklass av DbContext, som en begränsad tjänst i ASP.NET Core-apptjänstleverantören. Tjänstleverantören kallas även för containern för beroendeinmatning. Kontexten är konfigurerad för att använda SQL Server-databasprovidern och läser anslutningssträngen från ASP.NET Core-konfigurationen.

Klassen ApplicationDbContext måste exponera en offentlig konstruktor med en DbContextOptions<ApplicationDbContext> parameter. Det är så kontextkonfiguration från AddDbContext skickas DbContexttill . Till exempel:

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

ApplicationDbContext kan användas i ASP.NET Core-styrenheter eller andra tjänster genom konstruktorinmatning:

public class MyController
{
    private readonly ApplicationDbContext _context;

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

Slutresultatet är en ApplicationDbContext instans som skapas för varje begäran och skickas till kontrollanten för att utföra en arbetsenhet innan den tas bort när begäran upphör.

Läs mer i den här artikeln om du vill veta mer om konfigurationsalternativ. Mer information finns i Beroendeinmatning i ASP.NET Core .

Grundläggande DbContext-initiering med "ny"

DbContext instanser kan konstrueras med new i C#. Konfigurationen kan utföras genom att skicka alternativ till konstruktorn eller genom att OnConfiguring åsidosätta metoden. Till exempel:

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

Det här mönstret gör det också enkelt att skicka konfigurationer, såsom anslutningssträngen, via konstruktorn. Till exempel:

public class ApplicationDbContext : DbContext
{
    private readonly string _connectionString;

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

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

DbContextOptionsBuilder Alternativt kan användas för att skapa ett DbContextOptions objekt som sedan skickas till DbContext konstruktorn. Detta gör att en DbContext konfigurerad för beroendeinmatning också kan konstrueras explicit. När du till exempel använder ApplicationDbContext som definierats för ASP.NET Core-webbappar ovan:

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

DbContextOptions Kan skapas och konstruktorn kan anropas explicit:

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

using var context = new ApplicationDbContext(contextOptions);

Använda en DbContext-fabrik

Vissa programtyper (t.ex. ASP.NET Core Blazor) använder beroendeinmatning men skapar inte ett tjänstomfång som överensstämmer med önskad DbContext livslängd. Även om en sådan justering finns kan programmet behöva utföra flera arbetsenheter inom det här omfånget. Till exempel flera arbetsenheter i en enda HTTP-begäran.

I dessa fall kan AddDbContextFactory användas för att registrera en fabrik för att skapa DbContext-instanser. Till exempel:

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

Klassen ApplicationDbContext måste exponera en offentlig konstruktor med en DbContextOptions<ApplicationDbContext> parameter. Det här är samma mönster som i det traditionella ASP.NET Core-avsnittet ovan.

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

Fabriken DbContextFactory kan sedan användas i andra tjänster genom konstruktorinmatning. Till exempel:

private readonly IDbContextFactory<ApplicationDbContext> _contextFactory;

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

Den injicerade fabriken kan sedan användas för att konstruera DbContext-instanser i tjänstkoden. Till exempel:

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

Observera att de DbContext instanser som skapas på det här sättet inte hanteras av programmets tjänstleverantör och därför måste tas bort av programmet.

Mer information om hur du använder EF Core med Blazor finns i ASP.NET Core Blazor Server med Entity Framework Core .

DbContextOptions

Startpunkten för all DbContext konfiguration är DbContextOptionsBuilder. Det finns tre sätt att skaffa det här byggverktyget:

  • I AddDbContext och relaterade metoder
  • I OnConfiguring
  • Konstruerad explicit med new

Exempel på var och en av dessa visas i föregående avsnitt. Samma konfiguration kan tillämpas oavsett var byggverktyget kommer ifrån. Dessutom OnConfiguring anropas alltid oavsett hur kontexten konstrueras. Detta innebär att OnConfiguring kan användas för att utföra ytterligare konfiguration även när AddDbContext används.

Konfigurera databasleverantören

Varje DbContext instans måste konfigureras för att använda en och endast en databasprovider. (Olika instanser av en DbContext undertyp kan användas med olika databasprovidrar, men en enda instans får bara använda en.) En databasprovider konfigureras med ett specifikt Use* anrop. Om du till exempel vill använda SQL Server-databasprovidern:

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

Dessa Use* metoder är tilläggsmetoder som implementeras av databasprovidern. Det innebär att databasleverantörens NuGet-paket måste installeras innan tilläggsmetoden kan användas.

Tips/Råd

EF Core-databasprovidrar använder sig i stor utsträckning av tilläggsmetoder. Om kompilatorn anger att det inte går att hitta en metod kontrollerar du att providerns NuGet-paket är installerat och att du har using Microsoft.EntityFrameworkCore; i koden.

Följande tabell innehåller exempel för vanliga databasprovidrar.

Databassystem Exempelkonfiguration NuGet-paket
SQL Server eller Azure SQL .UseSqlServer(connectionString) Microsoft.EntityFrameworkCore.SqlServer
Azure Cosmos DB .UseCosmos(connectionString, databaseName) Microsoft.EntityFrameworkCore.Cosmos
SQLite .UseSqlite(connectionString) Microsoft.EntityFrameworkCore.Sqlite
EF Core-databas i minnet .UseInMemoryDatabase(databaseName) Microsoft.EntityFrameworkCore.InMemory
PostgreSQL* .UseNpgsql(connectionString) Npgsql.EntityFrameworkCore.PostgreSQL
MySQL/MariaDB* .UseMySql(connectionString) Pomelo.EntityFrameworkCore.MySql
Orakel* .UseOracle(connectionString) Oracle.EntityFrameworkCore

*Dessa databasprovidrar levereras inte av Microsoft. Mer information om databasprovidrar finns i Databasprovidrar .

Varning

EF Core-databasen i minnet är inte avsedd för produktionsanvändning. Dessutom kanske det inte är det bästa valet även för testning. Mer information finns i Testa kod som använder EF Core .

Mer information om hur du använder anslutningssträngar med EF Core finns i Anslutningssträngar .

Valfri konfiguration som är specifik för databasprovidern utförs i ytterligare en providerspecifik byggare. Du kan till exempel använda EnableRetryOnFailure för att konfigurera återförsök för anslutningsåterhämtning vid anslutning till Azure SQL:

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

Tips/Råd

Samma databasprovider används för SQL Server och Azure SQL. Vi rekommenderar dock att anslutningsåterhämtning används när du ansluter till SQL Azure.

Mer information om providerspecifik konfiguration finns i Databasprovidrar .

Annan DbContext-konfiguration

Annan DbContext konfiguration kan kedjas antingen före eller efter (det spelar ingen roll vilket) Use* anropet. Om du till exempel vill aktivera loggning av känsliga data:

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

Följande tabell innehåller exempel på vanliga metoder som kallas för DbContextOptionsBuilder.

DbContextOptionsBuilder-metod Vad det gör Lära sig mer
UseQueryTrackingBehavior Anger standardspårningsbeteendet för frågor Beteende för frågespårning
LogTo Ett enkelt sätt att hämta EF Core-loggar Loggning, händelser och diagnostik
UseLoggerFactory Registrerar en Microsoft.Extensions.Logging fabrik Loggning, händelser och diagnostik
EnableSensitiveDataLogging Innehåller programdata i undantag och loggning Loggning, händelser och diagnostik
EnableDetailedErrors Mer detaljerade frågefel (på bekostnad av prestanda) Loggning, händelser och diagnostik
ConfigureWarnings Ignorera eller kasta för varningar och andra händelser Loggning, händelser och diagnostik
AddInterceptors Registrerar EF Core-avlyssningsappar Loggning, händelser och diagnostik
EnableServiceProviderCaching Styr cachelagring av den interna tjänstleverantören Cachelagring av tjänstleverantör
UseMemoryCache Konfigurerar minnescachen som används av EF Core Integrering av minnescachen
UseLazyLoadingProxies Använd dynamiska proxyer för lazy loading Lazy Loading
UseChangeTrackingProxies Använda dynamiska proxyservrar för ändringsspårning Kommer snart...

Anmärkning

UseLazyLoadingProxies och UseChangeTrackingProxies är tilläggsmetoder från NuGet-paketet Microsoft.EntityFrameworkCore.Proxies . Den här typen av ". UseSomething()"-anrop är det rekommenderade sättet att konfigurera och/eller använda EF Core-tillägg som finns i andra paket.

DbContextOptions jämfört med DbContextOptions<TContext>

De flesta DbContext underklasser som accepterar en DbContextOptions bör använda den allmännaDbContextOptions<TContext> varianten. Till exempel:

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

Detta säkerställer att rätt alternativ för den specifika DbContext undertypen bestäms från beroendeinjektion, även när flera DbContext undertyper är registrerade.

Tips/Råd

** DbContext behöver inte vara sluten, men att försegla dem är bäst praxis för klasser som inte är avsedda att ärvas.

Men om DbContext undertypen är avsedd att ärvas från bör den exponera en skyddad konstruktor som tar en icke-generisk DbContextOptions. Till exempel:

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

På så sätt kan flera konkreta underklasser anropa den här baskonstruktorn med hjälp av sina olika generiska DbContextOptions<TContext> instanser. Till exempel:

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

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

Observera att detta är exakt samma mönster som när du ärver från DbContext direkt. DbContext Konstruktorn accepterar alltså en icke-generisk DbContextOptions av den anledningen.

En DbContext underklass som är avsedd att både instansieras och ärvas från bör exponera båda formerna av konstruktor. Till exempel:

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

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

Design-time DbContext-konfiguration

EF Core-designtidsverktyg som de för EF Core-migreringar måste kunna identifiera och skapa en fungerande instans av en DbContext typ för att samla in information om programmets entitetstyper och hur de mappar till ett databasschema. Den här processen kan vara automatisk så länge verktyget enkelt kan skapa DbContext på ett sådant sätt att det konfigureras på samma sätt som det skulle konfigureras vid körning.

Även om alla mönster som ger nödvändig konfigurationsinformation till DbContext kan fungera vid körning, kan verktyg som kräver användning av en DbContext vid designtid bara fungera med ett begränsat antal mönster. Dessa beskrivs mer detaljerat i Design-Time Kontextskapande.

Undvika problem med DbContext-trådning

Entity Framework Core stöder inte flera parallella åtgärder som körs på samma DbContext instans. Detta omfattar både parallell körning av asynkrona frågor och explicit samtidig användning från flera trådar. Därför gör du alltid await asynkrona anrop omedelbart, eller använder separata DbContext instanser för åtgärder som körs parallellt.

När EF Core identifierar ett försök att använda en DbContext instans samtidigt visas ett InvalidOperationException med ett meddelande som liknar detta:

En andra åtgärd startade i den här kontexten innan en tidigare åtgärd slutfördes. Detta orsakas vanligtvis av olika trådar som använder samma instans av DbContext, men instansmedlemmar garanteras inte vara trådsäkra.

När samtidig åtkomst inte identifieras kan det leda till odefinierat beteende, programkrascher och skadade data.

Det finns vanliga misstag som oavsiktligt kan orsaka samtidig åtkomst på samma DbContext instans:

Fallgropar för asynkrona åtgärder

Med asynkrona metoder kan EF Core initiera åtgärder som har åtkomst till databasen på ett icke-blockerande sätt. Men om en anropare inte inväntar slutförandet av någon av dessa metoder och fortsätter att utföra andra åtgärder på DbContext, kan tillståndet DbContext komma att bli (och med stor sannolikhet kommer att vara) skadat.

Vänta alltid på EF Core-asynkrona metoder omedelbart.

Implicit delning av DbContext-instanser via beroendeinmatning

Tilläggsmetoden AddDbContext registrerar DbContext typer med en begränsad livslängd som standard.

Detta är säkert från samtidiga åtkomstproblem i de flesta ASP.NET Core-program eftersom det bara finns en tråd som kör varje klientbegäran vid en viss tidpunkt och eftersom varje begäran får ett separat omfång för beroendeinmatning (och därmed en separat DbContext instans). För Blazor Server-värdmodellen används en logisk begäran för att underhålla Blazor-användarkretsen, och därför är endast en begränsad DbContext-instans tillgänglig per användarkrets om standardinmatningsomfånget används.

All kod som uttryckligen kör flera trådar parallellt bör se till att DbContext instanser aldrig används samtidigt.

Med hjälp av beroendeinjektion kan detta uppnås genom att antingen registrera kontexten som omfattande och skapa omfång för varje tråd (med hjälp av IServiceScopeFactory), eller genom att registrera DbContext som tillfällig (med den överlagring av AddDbContext som tar en ServiceLifetime parameter).

Mer läsning