Durée de vie, configuration et initialisation de DbContext
Cet article présente des modèles de base pour l’initialisation et la configuration d’une instance DbContext.
Durée de vie de DbContext
La durée de vie de DbContext
commence lorsque l'instance est créée et se termine lorsque l'instance est supprimée. Une instance DbContext
est conçue pour être utilisée pour une seule unité de travail. Cela signifie que la durée de vie d’une instance DbContext
est généralement très courte.
Conseil
Pour citer Martin Fowler à partir du lien ci-dessus, « Une unité de travail effectue le suivi de tout ce que vous faites lors d’une transaction commerciale qui peut affecter la base de données. Lorsque vous avez terminé, il détermine tout ce qui doit être fait pour modifier la base de données à la suite de votre travail.
Une unité de travail classique lors de l’utilisation d’Entity Framework Core (EF Core) implique :
- Création d’une instance
DbContext
- Suivi des instances d’entité par le contexte. Les entités deviennent suivies par
- Retourné à partir d’une requête
- En cours d’ajout ou d’attachement au contexte
- Les modifications sont apportées aux entités suivies selon les besoins pour implémenter la règle métier
- SaveChanges ou SaveChangesAsync est appelé. EF Core détecte les modifications apportées et les écrit dans la base de données.
- L’instance
DbContext
est supprimée
Important
- Il est très important de supprimer DbContext après utilisation. Cela garantit que toutes les ressources non managées sont libérées et que tous les événements ou autres crochets ne sont pas enregistrés afin d’empêcher les fuites de mémoire dans le cas où l’instance reste référencée.
- DbContext n’est pas thread-safe. Ne partagez pas de contextes entre les threads. Veillez à attendre tous les appels asynchrones avant de continuer à utiliser l’instance de contexte.
- Un InvalidOperationException levé par un code EF Core peut placer le contexte dans un état irrécupérable. Ces exceptions indiquent une erreur de programme et ne sont pas conçues pour être récupérées.
DbContext dans l’injection de dépendances pour ASP.NET Core
Dans de nombreuses applications web, chaque requête HTTP correspond à une seule unité de travail. Cela rend la durée de vie du contexte liée à celle de la requête une bonne valeur par défaut pour les applications web.
Les applications ASP.NET Core sont configurées à l’aide de l’injection de dépendances. EF Core peut être ajouté à cette configuration à l’aide de AddDbContext dans la méthode ConfigureServices
de Startup.cs
. Par exemple :
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddDbContext<ApplicationDbContext>(
options => options.UseSqlServer("name=ConnectionStrings:DefaultConnection"));
}
Cet exemple enregistre une sous-classe DbContext
appelée ApplicationDbContext
en tant que service délimité dans le fournisseur de services d’application ASP.NET Core (a.k.a. le conteneur d’injection de dépendances). Le contexte est configuré pour utiliser le fournisseur de base de données SQL Server et lit la chaîne de connexion à partir de ASP.NET Core configuration. Il n’est généralement pas important où dans ConfigureServices
l’appel à AddDbContext
est effectué.
La classe ApplicationDbContext
doit exposer un constructeur public avec un paramètre DbContextOptions<ApplicationDbContext>
. Il s’agit de la façon dont la configuration de contexte à partir de AddDbContext
est passée au DbContext
. Par exemple :
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
ApplicationDbContext
peut ensuite être utilisé dans ASP.NET Core contrôleurs ou d’autres services par injection de constructeur. Par exemple :
public class MyController
{
private readonly ApplicationDbContext _context;
public MyController(ApplicationDbContext context)
{
_context = context;
}
}
Le résultat final est une instance ApplicationDbContext
créée pour chaque requête et transmise au contrôleur pour effectuer une unité de travail avant d’être supprimée lorsque la requête se termine.
Pour en savoir plus sur les options de configuration, consultez cet article. En outre, consultez Démarrage de l’application dans ASP.NET Core et Injection de dépendances dans ASP.NET Core pour plus d’informations sur la configuration et l’injection de dépendances dans ASP.NET Core.
Initialisation simple DbContext avec « new »
Les instances DbContext
peuvent être construites de la manière .NET normale, par exemple avec new
en C#. La configuration peut être effectuée en remplaçant la méthode OnConfiguring
ou en passant des options au constructeur. Par exemple :
public class ApplicationDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");
}
}
Ce modèle facilite également la transmission de la configuration comme la chaîne de connexion via le constructeur DbContext
. Par exemple :
public class ApplicationDbContext : DbContext
{
private readonly string _connectionString;
public ApplicationDbContext(string connectionString)
{
_connectionString = connectionString;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(_connectionString);
}
}
Vous pouvez également utiliser DbContextOptionsBuilder
pour créer un objet DbContextOptions
qui est ensuite passé au constructeur DbContext
. Cela permet à un DbContext
configuré pour l’injection de dépendances d’être construit explicitement. Par exemple, lors de l’utilisation de ApplicationDbContext
définie pour les applications web ASP.NET Core ci-dessus :
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
DbContextOptions
peut être créé et le constructeur peut être appelé explicitement :
var contextOptions = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0")
.Options;
using var context = new ApplicationDbContext(contextOptions);
Utilisation d’une fabrique DbContext (par exemple, pour Blazor)
Certains types d’application (par exemple, ASP.NET Core Blazor) utilisent l’injection de dépendances, mais ne créent pas d’étendue de service qui s’aligne sur la durée de vie souhaitéeDbContext
. Même lorsqu’un tel alignement existe, l’application peut avoir besoin d’effectuer plusieurs unités de travail dans cette étendue. Par exemple, plusieurs unités de travail au sein d’une seule requête HTTP.
Dans ces cas, vous pouvez utiliser AddDbContextFactory pour inscrire une fabrique pour la création d’instances DbContext
. Par exemple :
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextFactory<ApplicationDbContext>(
options =>
options.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0"));
}
La classe ApplicationDbContext
doit exposer un constructeur public avec un paramètre DbContextOptions<ApplicationDbContext>
. Il s’agit du même modèle que celui utilisé dans la section ASP.NET Core traditionnelle ci-dessus.
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
}
La fabrique DbContextFactory
peut ensuite être utilisée dans d’autres services par injection de constructeur. Par exemple :
private readonly IDbContextFactory<ApplicationDbContext> _contextFactory;
public MyController(IDbContextFactory<ApplicationDbContext> contextFactory)
{
_contextFactory = contextFactory;
}
La fabrique injectée peut ensuite être utilisée pour construire des instances DbContext dans le code de service. Par exemple :
public void DoSomething()
{
using (var context = _contextFactory.CreateDbContext())
{
// ...
}
}
Notez que les instances DbContext
créées de cette façon ne sont pas gérées par le fournisseur de services de l’application et doivent donc être supprimées par l’application.
Consultez ASP.NET Core Blazor Server avec Entity Framework Core pour plus d’informations sur l’utilisation d’EF Core avec Blazor.
DbContextOptions
Le point de départ de toute configuration DbContext
est DbContextOptionsBuilder. Il existe trois façons d’utiliser ce générateur :
- Dans
AddDbContext
et les méthodes associées - Dans
OnConfiguring
- Construit explicitement avec
new
Des exemples de chacun d’entre eux sont affichés dans les sections précédentes. La même configuration peut être appliquée, quel que soit l’endroit où provient le générateur. En outre, OnConfiguring
est toujours appelé indépendamment de la façon dont le contexte est construit. Cela signifie que OnConfiguring
peut être utilisé pour effectuer une configuration supplémentaire même quand AddDbContext
est utilisé.
Configuration du fournisseur de base de données
Chaque instance DbContext
doit être configurée pour utiliser un et un seul fournisseur de base de données. (Différentes instances d’un sous-type DbContext
peuvent être utilisées avec différents fournisseurs de base de données, mais une seule instance ne doit utiliser qu’une seule.) Un fournisseur de base de données est configuré à l’aide d’un appel spécifique Use*
. Par exemple, pour utiliser le fournisseur de base de données SQL Server :
public class ApplicationDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");
}
}
Ces méthodes Use*
sont des méthodes d’extension implémentées par le fournisseur de base de données. Cela signifie que le package NuGet du fournisseur de base de données doit être installé avant que la méthode d’extension puisse être utilisée.
Conseil
Les fournisseurs de base de données EF Core utilisent largement les méthodes d’extension. Si le compilateur indique qu’une méthode est introuvable, vérifiez que le package NuGet du fournisseur est installé et que vous avez using Microsoft.EntityFrameworkCore;
dans votre code.
Le tableau suivant contient des exemples pour les fournisseurs de base de données courants.
Système de base de données | Exemple de configuration | Package NuGet |
---|---|---|
SQL Server ou SQL Azure | .UseSqlServer(connectionString) |
Microsoft.EntityFrameworkCore.SqlServer |
Azure Cosmos DB | .UseCosmos(connectionString, databaseName) |
Microsoft.EntityFrameworkCore.Cosmos |
SQLite | .UseSqlite(connectionString) |
Microsoft.EntityFrameworkCore.Sqlite |
Fournisseur en mémoire EF Core | .UseInMemoryDatabase(databaseName) |
Microsoft.EntityFrameworkCore.InMemory |
PostgreSQL* | .UseNpgsql(connectionString) |
Npgsql.EntityFrameworkCore.PostgreSQL |
MySQL/MariaDB* | .UseMySql(connectionString) |
Pomelo.EntityFrameworkCore.MySql |
Oracle* | .UseOracle(connectionString) |
Oracle.EntityFrameworkCore |
*Ces fournisseurs de base de données ne sont pas expédiés par Microsoft. Pour plus d’informations sur les fournisseurs de base de données, consultez fournisseurs de base de données.
Avertissement
La base de données EF Core en mémoire n’est pas conçue pour une utilisation en production. En outre, il peut ne pas être le meilleur choix même pour les tests. Pour plus d’informations, consultez Test du code qui utilise EF Core.
Pour plus d’informations sur l’utilisation de chaînes de connexion avec EF Core, consultez Chaînes de connexion.
Une configuration facultative spécifique au fournisseur de base de données est effectuée dans un générateur supplémentaire spécifique au fournisseur. Par exemple, l’utilisation de EnableRetryOnFailure pour configurer les nouvelles tentatives de résilience de connexion lors de la connexion à Azure SQL :
public class ApplicationDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(
@"Server=(localdb)\mssqllocaldb;Database=Test",
providerOptions => { providerOptions.EnableRetryOnFailure(); });
}
}
Conseil
Le même fournisseur de base de données est utilisé pour SQL Server et Azure SQL. Toutefois, il est recommandé d’utiliser la résilience de connexion lors de la connexion à SQL Azure.
Pour plus d’informations sur la configuration spécifique au fournisseur, consultez Fournisseurs de base de données.
Autre configuration DbContext
D’autres configurations DbContext
peuvent être chaînées avant ou après (il n’y a aucune différence qui) l’appel Use*
. Par exemple, pour activer la journalisation des données sensibles :
public class ApplicationDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.EnableSensitiveDataLogging()
.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");
}
}
Le tableau suivant contient des exemples de méthodes courantes appelées sur DbContextOptionsBuilder
.
Méthode DbContextOptionsBuilder | Qu’est-ce que cela fait ? | En savoir plus |
---|---|---|
UseQueryTrackingBehavior | Définit le comportement de suivi par défaut pour les requêtes | Comportement du suivi des requêtes |
LogTo | Un moyen simple d’obtenir les journaux EF Core | Journalisation, événements et diagnostics |
UseLoggerFactory | Inscrit une fabrique Microsoft.Extensions.Logging |
Journalisation, événements et diagnostics |
EnableSensitiveDataLogging | Inclut les données d’application dans les exceptions et la journalisation | Journalisation, événements et diagnostics |
EnableDetailedErrors | Erreurs de requête plus détaillées (au détriment des performances) | Journalisation, événements et diagnostics |
ConfigureWarnings | Ignorer ou lever pour les avertissements et autres événements | Journalisation, événements et diagnostics |
AddInterceptors | Inscrit les intercepteurs EF Core | Journalisation, événements et diagnostics |
UseLazyLoadingProxies | Utiliser des proxys dynamiques pour le chargement différé | Chargement différé |
UseChangeTrackingProxies | Utiliser des proxys dynamiques pour le suivi des modifications | Bientôt disponible... |
Remarque
UseLazyLoadingProxies et UseChangeTrackingProxies sont des méthodes d’extension du package NuGet Microsoft.EntityFrameworkCore.Proxies. Ce genre de l’appel « .UseSomething() » est la méthode recommandée pour configurer et/ou utiliser des extensions EF Core contenues dans d’autres packages.
DbContextOptions
contre DbContextOptions<TContext>
La plupart des sous-classes DbContext
qui acceptent un DbContextOptions
doivent utiliser la variante générique DbContextOptions<TContext>
. Par exemple :
public sealed class SealedApplicationDbContext : DbContext
{
public SealedApplicationDbContext(DbContextOptions<SealedApplicationDbContext> contextOptions)
: base(contextOptions)
{
}
}
Cela garantit que les options correctes pour le sous-type DbContext
spécifique sont résolues à partir de l’injection de dépendances, même lorsque plusieurs sous-types DbContext
sont inscrits.
Conseil
Votre DbContext n’a pas besoin d’être scellé, mais il est recommandé de le faire pour les classes qui ne sont pas conçues pour être héritées.
Toutefois, si le sous-type DbContext
est lui-même destiné à être hérité, il doit exposer un constructeur protégé prenant un constructeur non générique DbContextOptions
. Par exemple :
public abstract class ApplicationDbContextBase : DbContext
{
protected ApplicationDbContextBase(DbContextOptions contextOptions)
: base(contextOptions)
{
}
}
Cela permet à plusieurs sous-classes concrètes d’appeler ce constructeur de base à l’aide de leurs différentes instances génériques DbContextOptions<TContext>
. Par exemple :
public sealed class ApplicationDbContext1 : ApplicationDbContextBase
{
public ApplicationDbContext1(DbContextOptions<ApplicationDbContext1> contextOptions)
: base(contextOptions)
{
}
}
public sealed class ApplicationDbContext2 : ApplicationDbContextBase
{
public ApplicationDbContext2(DbContextOptions<ApplicationDbContext2> contextOptions)
: base(contextOptions)
{
}
}
Notez que c’est exactement le même modèle que lors de l’héritage direct de DbContext
. Autrement dit, le constructeur DbContext
lui-même accepte un DbContextOptions
non générique pour cette raison.
Une sous-classe DbContext
destinée à être instanciée et héritée doit exposer les deux formes de constructeur. Par exemple :
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> contextOptions)
: base(contextOptions)
{
}
protected ApplicationDbContext(DbContextOptions contextOptions)
: base(contextOptions)
{
}
}
Configuration DbContext au moment du design
Les outils de conception EF Core tels que ceux pour les migrations EF Core doivent être en mesure de découvrir et de créer une instance de travail d’un type DbContext
afin de collecter des détails sur les types d’entités de l’application et la façon dont ils mappent à un schéma de base de données. Ce processus peut être automatique tant que l’outil peut facilement créer le DbContext
de manière à ce qu’il soit configuré de la même façon qu’il sera configuré au moment de l’exécution.
Bien que n’importe quel modèle qui fournit les informations de configuration nécessaires au DbContext
peut fonctionner au moment de l’exécution , les outils qui nécessitent l’utilisation d’un DbContext
au moment du design ne peuvent fonctionner qu’avec un nombre limité de modèles. Ceux-ci sont abordés plus en détail dans la création de contexte au moment du design.
Éviter les problèmes de threading DbContext
Entity Framework Core ne prend pas en charge les opérations parallèles multiples en cours d’exécution sur la même instance DbContext
. Cela inclut l’exécution parallèle de requêtes asynchrones et toute utilisation simultanée explicite de plusieurs threads. Par conséquent, les appels asynchrones await
sont toujours immédiats ou utilisent des instances distinctes DbContext
pour les opérations qui s’exécutent en parallèle.
Lorsque EF Core détecte une tentative d’utilisation simultanée d’une instance DbContext
, un InvalidOperationException
avec un message semblable à celui-ci s’affiche :
Une deuxième opération a démarré sur ce contexte avant la fin d’une opération précédente. Cela est généralement dû à différents threads utilisant la même instance de DbContext, mais les membres de l’instance ne sont pas garantis comme thread-safe.
Lorsque l’accès simultané n’est pas détecté, il peut entraîner un comportement non défini, des incidents d’application et une altération des données.
Il existe des erreurs courantes qui peuvent provoquer par inadvertance un accès simultané sur la même instance DbContext
:
Pièges d’opération asynchrone
Les méthodes asynchrones permettent à EF Core de lancer des opérations qui accèdent à la base de données de manière non bloquante. Toutefois, si un appelant n’attend pas l’achèvement de l’une de ces méthodes et procède à d’autres opérations sur le DbContext
, l’état du DbContext
peut être (et très probablement) endommagé.
Attendez toujours immédiatement les méthodes asynchrones EF Core.
Partage implicite d’instances DbContext via l’injection de dépendances
La méthode d’extension AddDbContext
inscrit les types DbContext
avec une durée de vie limitée par défaut.
Cela est sans risque contre les problèmes d’accès simultané dans la plupart des applications ASP.NET Core, car il n’existe qu’un seul thread exécutant chaque requête client à un moment donné, et parce que chaque requête obtient une étendue d’injection de dépendance distincte (et donc une instance distincteDbContext
). Pour le modèle d’hébergement Blazor Server, une requête logique est utilisée pour maintenir le circuit utilisateur Blazor, et par conséquent, une seule instance DbContext étendue est disponible par circuit utilisateur si l’étendue d’injection par défaut est utilisée.
Tout code qui exécute explicitement plusieurs threads en parallèle doit s’assurer que les instances DbContext
ne sont jamais accessibles simultanément.
À l’aide de l’injection de dépendances, cela peut être obtenu en inscrivant le contexte en tant qu’étendue et en créant des étendues (à l’aide de IServiceScopeFactory
) pour chaque thread, ou en inscrivant le DbContext
comme temporaire (à l’aide de la surcharge de AddDbContext
qui prend un paramètre ServiceLifetime
).
Plus de lecture
- Lisez Injection de dépendances pour en savoir plus sur l’utilisation de l’injection de dépendances.
- Lisez Tests pour plus d’informations.