Lors d'un entretien d'embauche, on m'a demandé d'expliquer pourquoi le modèle de référentiel n'était pas un modèle approprié pour fonctionner avec des ORM tels qu'Entity Framework. pourquoi est-ce le cas?
Lors d'un entretien d'embauche, on m'a demandé d'expliquer pourquoi le modèle de référentiel n'était pas un modèle approprié pour fonctionner avec des ORM tels qu'Entity Framework. pourquoi est-ce le cas?
Réponses:
Je ne vois aucune raison pour que le modèle de référentiel ne fonctionne pas avec Entity Framework. Le modèle de référentiel est une couche d'abstraction que vous mettez sur votre couche d'accès aux données. Votre couche d'accès aux données peut être composée de procédures stockées ADO.NET pures, d'Entity Framework ou d'un fichier XML.
Dans les grands systèmes, où vous avez des données provenant de différentes sources (base de données / XML / service Web), il est bon de disposer d'une couche d'abstraction. Le modèle de référentiel fonctionne bien dans ce scénario. Je ne crois pas qu'Entity Framework est suffisamment abstrait pour cacher ce qui se passe dans les coulisses.
J'ai utilisé le modèle de référentiel avec Entity Framework comme méthode de couche d'accès aux données et je n'ai pas encore rencontré de problème.
Un autre avantage de la synthèse DbContext
avec un référentiel est la testabilité par unité . Vous pouvez avoir votre IRepository
interface qui a 2 implémentations, une (le référentiel réel) qui utilise DbContext
pour parler à la base de données et la seconde, FakeRepository
qui peut renvoyer des objets en mémoire / des données fictives. Cela rend votre IRepository
unité testable, donc d'autres parties de code qui utilise IRepository
.
public interface IRepository
{
IEnumerable<CustomerDto> GetCustomers();
}
public EFRepository : IRepository
{
private YourDbContext db;
private EFRepository()
{
db = new YourDbContext();
}
public IEnumerable<CustomerDto> GetCustomers()
{
return db.Customers.Select(f=>new CustomerDto { Id=f.Id, Name =f.Name}).ToList();
}
}
public MockRepository : IRepository
{
public IEnumerable<CustomerDto> GetCustomers()
{
// to do : return a mock list of Customers
// Or you may even use a mocking framework like Moq
}
}
Maintenant, en utilisant DI, vous obtenez la mise en œuvre
public class SomeService
{
IRepository repo;
public SomeService(IRepository repo)
{
this.repo = repo;
}
public void SomeMethod()
{
//use this.repo as needed
}
}
La meilleure raison de ne pas utiliser le modèle de référentiel avec Entity Framework? Entity Framework implémente déjà un modèle de référentiel. DbContext
est votre unité de travail (UoW) et chacun DbSet
est le référentiel. L'implémentation d'une autre couche par-dessus est non seulement redondante, mais rend la maintenance plus difficile.
Les gens suivent des modèles sans se rendre compte de leur objectif . Dans le cas du modèle de référentiel, le but est d'extraire la logique d'interrogation de base de données de bas niveau. Auparavant, lorsque vous écriviez des instructions SQL dans votre code, le modèle de référentiel était un moyen de déplacer ce code SQL hors de méthodes individuelles dispersées dans votre base de code et de le localiser à un endroit. Avoir un ORM comme Entity Framework, NHibernate, etc. est un remplacement de cette abstraction de code et, en tant que tel, élimine la nécessité du modèle.
Cependant, ce n'est pas une mauvaise idée de créer une abstraction sur votre ORM, mais rien de plus complexe que UoW / repostitory. J'opterais pour un modèle de service, dans lequel vous construisez une API que votre application peut utiliser sans savoir si les données proviennent d'Entity Framework, de NHibernate ou d'une API Web. C'est beaucoup plus simple, vous ajoutez simplement des méthodes à votre classe de service pour renvoyer les données dont votre application a besoin. Si vous écriviez une application À faire, par exemple, vous pourriez avoir un appel de service pour renvoyer les éléments dus cette semaine et qui n'ont pas encore été terminés. Tout ce que votre application sait, c'est que si elle veut cette information, elle appelle cette méthode. Dans cette méthode et dans votre service en général, vous interagissez avec Entity Framework ou tout ce que vous utilisez. Ensuite, si vous décidez plus tard de changer d'ORM ou d'extraire les informations d'une API Web,
Cela peut sembler être un argument potentiel pour l’utilisation du modèle de référentiel, mais la différence essentielle réside dans le fait qu’un service est une couche plus fine et est conçu pour renvoyer des données entièrement préparées, plutôt que quelque chose que vous continuez à interroger, comme avec un dépôt.
DbContext
de EF6 + (voir: msdn.microsoft.com/en-us/data/dn314429.aspx ). Même dans les versions moins, vous pouvez utiliser une fausse DbContext
classe -comme avec moquaient DbSet
s, car DbSet
met en œuvre un iterface, IDbSet
.
Voici un extrait d’Ayende Rahien: L’ architecture dans le gouffre de Doom: les maux de la couche d’abstraction de référentiel
Je ne suis pas encore sûr d'être d'accord avec sa conclusion. C'est un catch-22 - d'un côté, si j'emballe mon contexte EF dans des référentiels spécifiques à un type avec des méthodes d'extraction de données spécifiques à une requête, je suis en mesure de tester mon code (en quelque sorte), ce qui est presque impossible avec Entity Cadre seul. D'autre part, je perds la capacité de faire des requêtes riches et de maintenir des relations sémantiques (mais même lorsque j'ai pleinement accès à ces fonctionnalités, j'ai toujours l'impression de marcher sur des œufs autour de EF ou de tout autre ORM que je pourrais choisir. , étant donné que je ne sais jamais quelles méthodes son implémentation IQueryable pourrait ou non prendre en charge, elle interprèterait mon ajout à une collection de propriétés de navigation comme une création ou simplement une association, qu'elle soit paresseuse ou impatiente ou ne se charge pas du tout par défaut, etc., alors peut-être que c'est pour le mieux. La "cartographie" objet-relationnelle à zéro impédance est quelque chose de créature mythologique - c'est peut-être pourquoi la dernière version de Entity Framework a été baptisée "Unicorn magique").
Cependant, récupérer vos entités à l'aide de méthodes d'extraction de données spécifiques à une requête signifie que vos tests unitaires sont désormais essentiellement des tests en boîte blanche et vous n'avez pas le choix, vous devez savoir à l'avance quelle méthode de référentiel est utilisée par l'unité testée. appeler pour s'en moquer. Et vous ne testez toujours pas les requêtes elles-mêmes, à moins d'écrire également des tests d'intégration.
Ce sont des problèmes complexes qui nécessitent une solution complexe. Vous ne pouvez pas y remédier simplement en prétendant que toutes vos entités sont des types séparés sans relations entre elles et en les atomisant chacune dans leur propre référentiel. Eh bien, vous pouvez , mais ça craint.
Mise à jour: j'ai eu un certain succès en utilisant le fournisseur Effort pour Entity Framework. Effort est un fournisseur en mémoire (open source) qui vous permet d’utiliser EF dans des tests exactement comme vous le feriez avec une vraie base de données. J'envisage de changer tous les tests de ce projet. Je travaille pour utiliser ce fournisseur, car cela semble rendre les choses beaucoup plus faciles. C’est la seule solution que j’ai trouvée jusqu’à présent qui réponde à tous les problèmes que j’avais soulevés tout à l’heure. La seule chose à faire est qu'il y a un léger retard lors du démarrage de mes tests car il crée la base de données en mémoire (il utilise un autre package appelé NMemory pour le faire), mais je ne vois pas cela comme un réel problème. Un article de Code Project décrit l'utilisation de Effort (par rapport à SQL CE) à des fins de test.
DbContext
. Quoi qu’il en soit, vous pouvez toujours vous moquer DbSet
, et c’est de toute façon le principe fondamental d’Entity Framework. DbContext
n’est guère plus qu’une classe pour héberger vos DbSet
propriétés (référentiels) dans un emplacement (unité de travail), en particulier dans un contexte de test unitaire, où l’initialisation de la base de données et les éléments de connexion ne sont de toute façon plus nécessaires.
La raison pour laquelle vous le feriez probablement est que c'est un peu redondant. Entity Framework vous offre une multitude d'avantages fonctionnels et de codage. C'est pourquoi vous l'utilisez. Si vous le prenez ensuite et que vous l'enveloppez dans un modèle de référentiel, vous perdez ces avantages, vous pouvez également utiliser n'importe quelle autre couche d'accès aux données.
En théorie, j'estime qu'il est logique d'encapsuler la logique de connexion à la base de données pour la rendre plus facilement réutilisable, mais comme le lien ci-dessous le montre, nos cadres modernes prennent essentiellement en charge cette tâche maintenant.
ISessionFactory
et que l’ ISession
on se moque facilement de lui), mais ce n’est pas si facile avec DbContext
, malheureusement ...
Une très bonne raison d'utiliser le modèle de référentiel est d'autoriser la séparation de votre logique métier et / ou de votre interface utilisateur de System.Data.Entity. Cela présente de nombreux avantages, y compris de réels avantages pour les tests unitaires, en permettant l’utilisation de Fakes ou de Mocks.
Nous avons eu des problèmes avec des instances DbContext Entity Framework dupliquées mais différentes, lorsqu'un conteneur IoC contenant des référentiels new () up par type (par exemple, une instance UserRepository et GroupRepository appelant chacune son propre IDbSet à partir de DBContext) peut parfois générer plusieurs contextes par demande. (dans un contexte MVC / web).
La plupart du temps, cela fonctionne toujours, mais lorsque vous ajoutez une couche de service par-dessus cela et que ces services supposent que les objets créés avec un contexte seront correctement attachés en tant que collections enfant à un nouvel objet dans un autre contexte, il échouera et parfois ne fonctionnera pas. t en fonction de la vitesse des commits.
Après avoir essayé un modèle de référentiel sur un petit projet, je vous déconseille vivement de l'utiliser. non pas parce que cela complique votre système, et non pas parce que se moquer de données est un cauchemar, mais parce que vos tests deviennent inutiles !!
Le mocking de données vous permet d'ajouter des détails sans en-têtes, d'ajouter des enregistrements qui ne respectent pas les contraintes de la base de données et de supprimer des entités que la base de données refuserait de supprimer. Dans le monde réel, une seule mise à jour peut affecter plusieurs tables, journaux, historique, résumés, etc., ainsi que des colonnes telles que le champ de la date de dernière modification, les clés générées automatiquement et les champs calculés.
En bref, exécuter votre test sur une base de données réelle vous donne des résultats concrets et vous pouvez tester non seulement vos services et interfaces, mais également le comportement de votre base de données. Vous pouvez vérifier si vos procédures stockées fonctionnent correctement avec les données, renvoyer le résultat attendu ou si l'enregistrement que vous avez envoyé à supprimer est réellement supprimé! De tels tests peuvent également exposer des problèmes tels que l’oubli des erreurs de procédure stockée et des milliers de tels scénarios.
Je pense que le framework entity met en œuvre le modèle de référentiel mieux que tous les articles que j'ai lus jusqu'à présent et qu'il va bien au-delà de ce qu'ils tentent d'accomplir.
Le référentiel était la meilleure pratique pour les jours où nous utilisions XBase, AdoX et Ado.Net, mais avec une entité !! (Référentiel sur référentiel)
Enfin, je pense que trop de personnes investissent beaucoup de temps dans l’apprentissage et la mise en œuvre de modèles de référentiels et refusent de les laisser tomber. Principalement pour se prouver qu'ils ne perdaient pas leur temps.
Cela est dû aux migrations: il n'est pas possible de faire fonctionner les migrations, car la chaîne de connexion réside dans le fichier web.config. Mais, DbContext réside dans la couche Repository. IDbContextFactory doit disposer d'une chaîne de configuration dans la base de données .But Il est impossible que les migrations obtiennent la chaîne de connexion à partir de web.config.
Il y a du travail autour mais je n'ai pas encore trouvé de solution propre pour ça!