Modèle de fournisseur Entity Framework 6
Le modèle de fournisseur Entity Framework permet à Entity Framework d’être utilisé avec différents types de serveur de base de données. Par exemple, un fournisseur peut être branché pour permettre à EF d’être utilisé sur Microsoft SQL Server, tandis qu’un autre fournisseur peut être branché pour permettre à EF d’être utilisé sur Microsoft SQL Server Compact Edition. Les fournisseurs d’EF6 dont nous sommes conscients sont disponibles sur la page fournisseurs Entity Framework.
Certaines modifications ont été nécessaires à la façon dont EF interagit avec les fournisseurs pour permettre à EF d’être publié sous une licence open source. Ces modifications nécessitent la reconstruction des fournisseurs EF sur les assemblages EF6, ainsi que de nouveaux mécanismes d’inscription du fournisseur.
Regénération
Avec EF6 le code principal qui faisait précédemment partie du .NET Framework est désormais livré en tant qu’assemblage OOB (out-of-band). Vous trouverez plus d’informations sur la création d’applications sur EF6 dans la page Mise à jour des applications pour EF6. Les fournisseurs devront également être reconstruits à l’aide de ces instructions.
Vue d’ensemble des types de fournisseurs
Un fournisseur EF est vraiment une collection de services spécifiques au fournisseur définis par les types CLR que ces services s’étendent (pour une classe de base) ou implémentent (pour une interface). Deux de ces services sont fondamentaux et nécessaires au fonctionnement d’EF. D’autres sont facultatifs et doivent uniquement être implémentés si des fonctionnalités spécifiques sont requises et/ou si les implémentations par défaut de ces services ne fonctionnent pas pour le serveur de base de données spécifique ciblé.
Types de fournisseurs fondamentaux
DbProviderFactory
EF dépend de l’utilisation d’un type dérivé de System.Data.Common.DbProviderFactory pour effectuer l’accès à toutes les bases de données de bas niveau. DbProviderFactory ne fait pas réellement partie d’EF, mais est plutôt une classe dans le .NET Framework qui sert un point d’entrée pour les fournisseurs ADO.NET qui peuvent être utilisés par EF, d’autres machines virtuelles O/R ou directement par une application pour obtenir des instances de connexions, de commandes, de paramètres et d’autres abstractions ADO.NET d’une manière indépendante d’un fournisseur. Pour plus d’informations sur DbProviderFactory, consultez la documentation MSDN pour ADO.NET.
DbProviderServices
EF dépend de la présence d’un type dérivé de DbProviderServices pour fournir des fonctionnalités supplémentaires requises par EF en plus des fonctionnalités déjà fournies par le fournisseur de ADO.NET. Dans les versions antérieures d’EF, la classe DbProviderServices faisait partie du .NET Framework et se trouvait dans l’espace de noms System.Data.Common. À compter d’EF6, cette classe fait désormais partie d’EntityFramework.dll et se trouve dans l’espace de noms System.Data.Entity.Core.Common.
Vous trouverez plus d’informations sur les fonctionnalités fondamentales d’une implémentation DbProviderServices sur MSDN. Toutefois, notez qu’au moment de la rédaction de ces informations n’est pas mis à jour pour EF6, bien que la plupart des concepts soient toujours valides. Les implémentations SQL Server et SQL Server Compact de DbProviderServices sont également vérifiées dans la base de code open source et peuvent servir de références utiles pour d’autres implémentations.
Dans les versions antérieures d’EF, l’implémentation dbProviderServices à utiliser a été obtenue directement auprès d’un fournisseur de ADO.NET. Cette opération a été effectuée en castant DbProviderFactory sur IServiceProvider et en appelant la méthode GetService. Cela a étroitement couplé le fournisseur EF à DbProviderFactory. Ce couplage a bloqué le déplacement d’EF hors du .NET Framework. Par conséquent, pour EF6, ce couplage étroit a été supprimé et une implémentation de DbProviderServices est désormais inscrite directement dans le fichier de configuration de l’application ou dans la configuration basée sur le code, comme décrit plus en détail la section Registering DbProviderServices ci-dessous.
Services supplémentaires
Outre les services fondamentaux décrits ci-dessus, il existe également de nombreux autres services utilisés par EF qui sont toujours ou parfois spécifiques au fournisseur. Les implémentations spécifiques au fournisseur par défaut de ces services peuvent être fournies par une implémentation DbProviderServices. Les applications peuvent également remplacer les implémentations de ces services ou fournir des implémentations lorsqu’un type DbProviderServices ne fournit pas de valeur par défaut. Cette procédure est décrite plus en détail dans la section Résolution des services supplémentaires ci-dessous.
Les types de services supplémentaires qu’un fournisseur peut intéresser un fournisseur sont répertoriés ci-dessous. Vous trouverez plus d’informations sur chacun de ces types de services dans la documentation de l’API.
IDbExecutionStrategy
Il s'agit d'un service optionnel qui permet à un fournisseur d'implémenter des tentatives ou d'autres comportements lorsque des requêtes et des commandes sont exécutées contre la base de données. Si aucune implémentation n’est fournie, EF exécute simplement les commandes et propage toutes les exceptions levées. Pour SQL Server, ce service est utilisé pour fournir une stratégie de nouvelle tentative qui est particulièrement utile lors de l’exécution sur des serveurs de base de données cloud tels que SQL Azure.
IDbConnectionFactory
Il s’agit d’un service facultatif qui permet à un fournisseur de créer des objets DbConnection par convention lorsqu’il ne reçoit qu’un nom de base de données. Notez que, bien que ce service puisse être résolu par une implémentation DbProviderServices, elle a été présente depuis EF 4.1 et peut également être définie explicitement dans le fichier de configuration ou dans le code. Le fournisseur aura uniquement la possibilité de résoudre ce service s’il est inscrit en tant que fournisseur par défaut (voir Le fournisseur par défaut ci-dessous) et si une fabrique de connexion par défaut n’a pas été définie ailleurs.
DbSpatialServices
Il s’agit d’un service facultatif qui permet à un fournisseur d’ajouter la prise en charge des types géographiques et géométriques spatiaux. Une implémentation de ce service doit être fournie pour qu’une application utilise EF avec des types spatiaux. DbSpatialServices est demandé de deux manières. Tout d'abord, les services spatiaux spécifiques au fournisseur sont demandés à l'aide d'un objet DbProviderInfo (qui contient le nom de l'invariant et le jeton de manifeste) comme clé. Ensuite, DbSpatialServices peut être demandé sans clé. Il est utilisé pour résoudre le « fournisseur spatial global » utilisé lors de la création de types DbGeography ou DbGeometry autonomes.
MigrationSqlGenerator
Il s’agit d’un service facultatif qui permet aux migrations EF d’être utilisées pour la génération de SQL utilisée dans la création et la modification de schémas de base de données par Code First. Une implémentation est nécessaire pour prendre en charge les migrations. Si une implémentation est fournie, elle sera également utilisée lorsque des bases de données sont créées à l’aide d’initialiseurs de base de données ou de la méthode Database.Create.
Func<DbConnection, string, HistoryContextFactory>
Il s’agit d’un service facultatif qui permet à un fournisseur de configurer le mappage de HistoryContext à la table __MigrationHistory
utilisée par EF Migrations. HistoryContext est un code First DbContext et peut être configuré à l’aide de l’API Fluent normale pour modifier les éléments tels que le nom de la table et les spécifications de mappage de colonnes. L’implémentation par défaut de ce service retournée par EF pour tous les fournisseurs peut fonctionner pour un serveur de base de données donné si tous les mappages de tables et de colonnes par défaut sont pris en charge par ce fournisseur. Dans ce cas, le fournisseur n’a pas besoin de fournir une implémentation de ce service.
IDbProviderFactoryResolver
Il s’agit d’un service facultatif pour obtenir le dbProviderFactory correct à partir d’un objet DbConnection donné. L’implémentation par défaut de ce service retournée par EF pour tous les fournisseurs est destinée à fonctionner pour tous les fournisseurs. Toutefois, lors de l’exécution sur .NET 4, DbProviderFactory n’est pas accessible publiquement à partir d’une instance si ses DbConnections. Par conséquent, EF utilise certaines heuristiques pour rechercher une correspondance auprès des fournisseurs inscrits. Il est possible que pour certains fournisseurs ces heuristiques échouent et, dans de telles situations, le fournisseur doit fournir une nouvelle implémentation.
Inscription de DbProviderServices
L’implémentation dbProviderServices à utiliser peut être inscrite dans le fichier de configuration de l’application (app.config ou web.config) ou à l’aide de la configuration basée sur le code. Dans les deux cas, l’inscription utilise le « nom invariant » du fournisseur comme clé. Cela permet à plusieurs fournisseurs d’être inscrits et utilisés dans une seule application. Le nom invariant utilisé pour les inscriptions EF est identique au nom invariant utilisé pour l’inscription et les chaînes de connexion du fournisseur ADO.NET. Par exemple, pour SQL Server, le nom invariant « System.Data.SqlClient » est utilisé.
Inscription dans le fichier config
Le type DbProviderServices à utiliser est inscrit en tant qu’élément fournisseur dans la liste des fournisseurs de la section entityFramework du fichier de configuration de l’application. Par exemple :
<entityFramework>
<providers>
<provider invariantName="My.Invariant.Name" type="MyProvider.MyProviderServices, MyAssembly" />
</providers>
</entityFramework>
La chaîne de type doit être le nom de type qualifié d’assembly de l’implémentation DbProviderServices à utiliser.
Inscription basée sur le code
À compter des fournisseurs EF6, vous pouvez également être inscrits à l’aide du code. Cela permet à un fournisseur EF d’être utilisé sans modification du fichier de configuration de l’application. Pour utiliser la configuration basée sur le code, une application doit créer une classe DbConfiguration, comme décrit dans la documentation de configuration basée sur le code. Le constructeur de la classe DbConfiguration doit ensuite appeler SetProviderServices pour inscrire le fournisseur EF. Par exemple :
public class MyConfiguration : DbConfiguration
{
public MyConfiguration()
{
SetProviderServices("My.New.Provider", new MyProviderServices());
}
}
Résolution des services supplémentaires
Comme mentionné ci-dessus dans la section Vue d’ensemble des types de fournisseurs, une classe DbProviderServices peut également être utilisée pour résoudre des services supplémentaires. Cela est possible, car DbProviderServices implémente IDbDependencyResolver et chaque type DbProviderServices inscrit est ajouté en tant que « programme de résolution par défaut ». Le mécanisme IDbDependencyResolver est décrit plus en détail dans la section Résolution des dépendances. Toutefois, il n’est pas nécessaire de comprendre tous les concepts de cette spécification pour résoudre des services supplémentaires dans un fournisseur.
La façon la plus courante pour un fournisseur de résoudre des services supplémentaires consiste à appeler DbProviderServices.AddDependencyResolver pour chaque service dans le constructeur de la classe DbProviderServices. Par exemple, SqlProviderServices (le fournisseur EF pour SQL Server) a du code similaire à celui-ci pour l’initialisation :
private SqlProviderServices()
{
AddDependencyResolver(new SingletonDependencyResolver<IDbConnectionFactory>(
new SqlConnectionFactory()));
AddDependencyResolver(new ExecutionStrategyResolver<DefaultSqlExecutionStrategy>(
"System.data.SqlClient", null, () => new DefaultSqlExecutionStrategy()));
AddDependencyResolver(new SingletonDependencyResolver<Func<MigrationSqlGenerator>>(
() => new SqlServerMigrationSqlGenerator(), "System.data.SqlClient"));
AddDependencyResolver(new SingletonDependencyResolver<DbSpatialServices>(
SqlSpatialServices.Instance,
k =>
{
var asSpatialKey = k as DbProviderInfo;
return asSpatialKey == null
|| asSpatialKey.ProviderInvariantName == ProviderInvariantName;
}));
}
Ce constructeur utilise les classes d’assistance suivantes :
- SingletonDependencyResolver : fournit un moyen simple de résoudre les services Singleton, c’est-à-dire les services pour lesquels la même instance est retournée chaque fois que GetService est appelé. Les services temporaires sont souvent inscrits en tant que fabrique singleton qui sera utilisée pour créer des instances temporaires à la demande.
- ExecutionStrategyResolver : programme de résolution spécifique au retour d’implémentations IExecutionStrategy.
Au lieu d’utiliser DbProviderServices.AddDependencyResolver, il est également possible de remplacer DbProviderServices.GetService et de résoudre directement des services supplémentaires. Cette méthode est appelée lorsque EF a besoin d’un service défini par un certain type et, dans certains cas, pour une clé donnée. La méthode doit retourner le service s’il peut, ou retourner null pour refuser de retourner le service et autoriser plutôt une autre classe à la résoudre. Par exemple, pour résoudre la fabrique de connexion par défaut, le code dans GetService peut ressembler à ceci :
public override object GetService(Type type, object key)
{
if (type == typeof(IDbConnectionFactory))
{
return new SqlConnectionFactory();
}
return null;
}
Ordre d’inscription
Lorsque plusieurs implémentations DbProviderServices sont inscrites dans le fichier de configuration d’une application, elles sont ajoutées en tant que résolveurs secondaires dans l’ordre dans lequel elles sont répertoriées. Étant donné que les résolveurs sont toujours ajoutés au début de la chaîne de programme de résolution secondaire, cela signifie que le fournisseur à la fin de la liste aura la possibilité de résoudre les dépendances avant les autres. (Cela peut sembler un peu contre-intuitif au début, mais il est logique si vous imaginez sortir chaque fournisseur de la liste et le empiler sur les fournisseurs existants.)
Cette commande n’a généralement pas d’importance, car la plupart des services de fournisseur sont spécifiques au fournisseur et clés par nom invariant du fournisseur. Toutefois, pour les services qui ne sont pas clés par le nom invariant du fournisseur ou une autre clé spécifique au fournisseur, le service sera résolu en fonction de cet ordre. Par exemple, si elle n’est pas explicitement définie différemment ailleurs, la fabrique de connexion par défaut provient du fournisseur le plus haut dans la chaîne.
Inscriptions de fichiers de configuration supplémentaires
Il est possible d’inscrire explicitement certains des services de fournisseur supplémentaires décrits ci-dessus directement dans le fichier de configuration d’une application. Lorsque cette opération est effectuée, l’inscription dans le fichier de configuration sera utilisée au lieu de tout élément retourné par la méthode GetService de l’implémentation DbProviderServices.
Inscription de la fabrique de connexion par défaut
À compter d’EF5, le package NuGet EntityFramework a automatiquement inscrit la fabrique de connexion SQL Express ou la fabrique de connexion LocalDb dans le fichier de configuration.
Par exemple :
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" >
</entityFramework>
Le type est le nom de type qualifié d’assembly pour la fabrique de connexion par défaut, qui doit implémenter IDbConnectionFactory.
Il est recommandé qu’un package NuGet fournisseur définisse la fabrique de connexion par défaut de cette façon lors de l’installation. Consultez Packages NuGet pour les fournisseurs ci-dessous.
Modifications supplémentaires apportées au fournisseur EF6
Modifications apportées au fournisseur spatial
Les fournisseurs qui prennent en charge les types spatiaux doivent désormais implémenter certaines méthodes supplémentaires sur les classes dérivant de DbSpatialDataReader :
public abstract bool IsGeographyColumn(int ordinal)
public abstract bool IsGeometryColumn(int ordinal)
Il existe également de nouvelles versions asynchrones de méthodes existantes qui sont recommandées pour être remplacées en tant que délégués d’implémentations par défaut aux méthodes synchrones et ne s’exécutent donc pas de manière asynchrone :
public virtual Task<DbGeography> GetGeographyAsync(int ordinal, CancellationToken cancellationToken)
public virtual Task<DbGeometry> GetGeometryAsync(int ordinal, CancellationToken cancellationToken)
Prise en charge native d’Enumerable.Contains
EF6 introduit un nouveau type d’expression, DbInExpression, qui a été ajouté pour résoudre les problèmes de performances liés à l’utilisation d’Enumerable.Contains dans les requêtes LINQ. La classe DbProviderManifest a une nouvelle méthode virtuelle, SupportsInExpression, appelée par EF pour déterminer si un fournisseur gère le nouveau type d’expression. Pour la compatibilité avec les implémentations de fournisseur existantes, la méthode retourne false. Pour tirer parti de cette amélioration, un fournisseur EF6 peut ajouter du code pour gérer DbInExpression et remplacer SupportsInExpression pour retourner true. Une instance de DbInExpression peut être créée en appelant la méthode DbExpressionBuilder.In. Une instance DbInExpression est composée d’une DbExpression, représentant généralement une colonne de table et une liste de DbConstantExpression à tester pour une correspondance.
Packages NuGet pour les fournisseurs
Une façon de rendre un fournisseur EF6 disponible consiste à le libérer en tant que package NuGet. L’utilisation d’un package NuGet présente les avantages suivants :
- Il est facile d’utiliser NuGet pour ajouter l’inscription du fournisseur au fichier de configuration de l’application
- Des modifications supplémentaires peuvent être apportées au fichier de configuration pour définir la fabrique de connexion par défaut afin que les connexions effectuées par convention utilisent le fournisseur inscrit
- NuGet gère l’ajout de redirections de liaison afin que le fournisseur EF6 continue de fonctionner même après la publication d’un nouveau package EF
Par exemple, il s’agit du package EntityFramework.SqlServerCompact inclus dans la base de code open source. Ce package fournit un bon modèle pour créer des packages NuGet du fournisseur EF.
Commandes PowerShell
Lorsque le package NuGet EntityFramework est installé, il inscrit un module PowerShell qui contient deux commandes très utiles pour les packages de fournisseur :
- Add-EFProvider ajoute une nouvelle entité pour le fournisseur dans le fichier de configuration du projet cible et vérifie qu’elle se trouve à la fin de la liste des fournisseurs inscrits.
- Add-EFDefaultConnectionFactory ajoute ou met à jour l’inscription defaultConnectionFactory dans le fichier de configuration du projet cible.
Ces deux commandes s’occupent de l’ajout d’une section entityFramework au fichier de configuration et de l’ajout d’une collection de fournisseurs si nécessaire.
Il est prévu que ces commandes soient appelées à partir du script NuGet install.ps1. Par exemple, install.ps1 pour le fournisseur SQL Compact ressemble à ceci :
param($installPath, $toolsPath, $package, $project)
Add-EFDefaultConnectionFactory $project 'System.Data.Entity.Infrastructure.SqlCeConnectionFactory, EntityFramework' -ConstructorArguments 'System.Data.SqlServerCe.4.0'
Add-EFProvider $project 'System.Data.SqlServerCe.4.0' 'System.Data.Entity.SqlServerCompact.SqlCeProviderServices, EntityFramework.SqlServerCompact'</pre>
Vous pouvez obtenir plus d’informations sur ces commandes à l’aide de get-help dans la fenêtre console du Gestionnaire de package.
Fournisseurs d’habillage
Un fournisseur d’habillage est un fournisseur EF et/ou ADO.NET qui encapsule un fournisseur existant pour l’étendre avec d’autres fonctionnalités telles que le profilage ou le suivi. Les fournisseurs de création de package de restrictions peuvent être inscrits de la manière normale, mais il est souvent plus pratique de configurer le fournisseur de création de package de restrictions lors de l’exécution en interceptant la résolution des services liés au fournisseur. L’événement statique OnLockingConfiguration sur la classe DbConfiguration peut être utilisé pour ce faire.
OnLockingConfiguration est appelé après qu’EF ait déterminé où toute la configuration EF pour le domaine d’application sera obtenue, mais avant qu’elle ne soit verrouillée à des fins d’utilisation. Au démarrage de l’application (avant l’utilisation d’EF), l’application doit inscrire un gestionnaire d’événements pour cet événement. (Nous envisageons d’ajouter la prise en charge de l’inscription de ce gestionnaire dans le fichier de configuration, mais cela n’est pas encore pris en charge.) Le gestionnaire d’événements doit ensuite effectuer un appel à ReplaceService pour chaque service qui doit être encapsulé.
Par exemple, pour encapsuler IDbConnectionFactory et DbProviderService, un gestionnaire semblable à celui-ci doit être inscrit :
DbConfiguration.OnLockingConfiguration +=
(_, a) =>
{
a.ReplaceService<DbProviderServices>(
(s, k) => new MyWrappedProviderServices(s));
a.ReplaceService<IDbConnectionFactory>(
(s, k) => new MyWrappedConnectionFactory(s));
};
Le service qui a été résolu et doit maintenant être encapsulé avec la clé utilisée pour résoudre le service sont passés au gestionnaire. Le gestionnaire peut ensuite encapsuler ce service et remplacer le service retourné par la version encapsulée.
Résolution d’une dbProviderFactory avec EF
DbProviderFactory est l’un des types de fournisseurs fondamentaux nécessaires par EF, comme décrit dans la section vue d’ensemble des types de fournisseurs ci-dessus. Comme mentionné précédemment, il ne s’agit pas d’un type EF et d’une inscription ne fait généralement pas partie de la configuration EF, mais il s’agit plutôt de l’inscription normale ADO.NET fournisseur dans le fichier machine.config et/ou le fichier de configuration de l’application.
Malgré cela, EF utilise toujours son mécanisme de résolution de dépendance normal lors de la recherche d’un DbProviderFactory à utiliser. Le programme de résolution par défaut utilise l’inscription normale ADO.NET dans les fichiers de configuration, ce qui est généralement transparent. Mais en raison du mécanisme de résolution de dépendance normal est utilisé, cela signifie qu’un IDbDependencyResolver peut être utilisé pour résoudre un DbProviderFactory même si l’inscription normale ADO.NET n’a pas été effectuée.
La résolution de DbProviderFactory de cette façon a plusieurs implications :
- Une application utilisant la configuration basée sur le code peut ajouter des appels dans sa classe DbConfiguration pour inscrire dbProviderFactory approprié. Cela est particulièrement utile pour les applications qui ne souhaitent pas (ou ne peuvent pas) utiliser une configuration basée sur des fichiers du tout.
- Le service peut être encapsulé ou remplacé à l’aide de ReplaceService, comme décrit dans la section Fournisseurs de création de package de restrictions ci-dessus
- Théoriquement, une implémentation DbProviderServices peut résoudre un DbProviderFactory.
Le point important à noter sur l’exécution de l’une de ces opérations est qu’ils affecteront uniquement la recherche de DbProviderFactory par EF. D’autres codes non EF peuvent toujours s’attendre à ce que le fournisseur de ADO.NET soit inscrit de façon normale et échoue si l’inscription est introuvable. Pour cette raison, il est normalement préférable qu’un DbProviderFactory soit inscrit de manière normale ADO.NET.
Services connexes
Si EF est utilisé pour résoudre un DbProviderFactory, il doit également résoudre les services IProviderInvariantName et IDbProviderFactoryResolver.
IProviderInvariantName est un service utilisé pour déterminer un nom invariant de fournisseur pour un type donné de DbProviderFactory. L’implémentation par défaut de ce service utilise l’inscription du fournisseur ADO.NET. Cela signifie que si le fournisseur ADO.NET n’est pas inscrit de la manière normale, car DbProviderFactory est résolu par EF, il sera également nécessaire de résoudre ce service. Notez qu’un programme de résolution pour ce service est automatiquement ajouté lors de l’utilisation de la méthode DbConfiguration.SetProviderFactory.
Comme décrit dans la section Vue d’ensemble des types de fournisseurs ci-dessus, iDbProviderFactoryResolver est utilisé pour obtenir la dbProviderFactory correcte à partir d’un objet DbConnection donné. L’implémentation par défaut de ce service lors de l’exécution sur .NET 4 utilise l’inscription du fournisseur ADO.NET. Cela signifie que si le fournisseur ADO.NET n’est pas inscrit de la manière normale, car DbProviderFactory est résolu par EF, il sera également nécessaire de résoudre ce service.