Lors d'un entretien d'embauche, on m'a demandé d'expliquer pourquoi le modèle de référentiel n'est pas un bon modèle pour travailler avec des ORM comme Entity Framework. pourquoi est-ce le cas?
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 n'importe quoi, des procédures stockées ADO.NET pures à Entity Framework ou 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 d'avoir une couche d'abstraction. Le modèle de référentiel fonctionne bien dans ce scénario. Je ne pense pas qu'Entity Framework soit suffisamment d'abstraction 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.
n autre avantage d'abstraction de DbContext
avec un référentiel est la testabilité unitaire. Vous pouvez avoir votre interface IRepository
à laquelle ont 2 implémentations, une (le vrai référentiel) qui utilise DbContext
pour parler à la base de données et la seconde, FakeRepository
qui peut retourner dans -mémoire objets/données simulées. Cela rend votre IRepository
testable à l'unité, donc d'autres parties de code qui utilisent 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 l'implémentation
public class SomeService
{
IRepository repo;
public SomeService(IRepository repo)
{
this.repo = repo;
}
public void SomeMethod()
{
//use this.repo as needed
}
}
La meilleure raison pour ne pas utiliser le modèle de référentiel avec Entity Framework? Entity Framework déjà implémente un modèle de référentiel. DbContext
est votre UoW (Unit of Work) et chaque DbSet
est le référentiel. L'implémentation d'une autre couche par-dessus n'est pas seulement redondante, mais rend la maintenance plus difficile.
Les gens suivent des modèles sans se rendre compte du but du modèle. Dans le cas du modèle de référentiel, le but est d'abstraire la logique d'interrogation de la base de données de bas niveau. Dans l'ancien temps de l'écriture d'instructions SQL dans votre code, le modèle de référentiel était un moyen de déplacer ce SQL hors des méthodes individuelles dispersées dans votre base de code et de le localiser en un seul endroit. Avoir un ORM comme Entity Framework, NHibernate, etc. est un remplacement pour cette abstraction de code, et en tant que tel, annule la nécessité du modèle.
Cependant, ce n'est pas une mauvaise idée de créer une abstraction au-dessus de votre ORM, tout simplement pas quelque chose d'aussi complexe que UoW/repostitory. J'irais avec un modèle de service, où vous construisez une API que votre application peut utiliser sans savoir ou se soucier si les données proviennent d'Entity Framework, NHibernate ou d'une API Web. C'est beaucoup plus simple, car 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 de tâches, par exemple, vous pourriez avoir un appel de service pour retourner des articles qui sont dus cette semaine et qui ne sont pas encore terminés. Tout ce que votre application sait, c'est que si elle veut ces informations, elle appelle cette méthode. Dans cette méthode et dans votre service en général, vous interagissez avec Entity Framework ou tout autre élément que vous utilisez. Ensuite, si vous décidez plus tard de changer d'ORM ou de retirer les informations d'une API Web, vous n'avez qu'à changer le service et le reste de votre code se déroule avec bonheur, rien de plus sage.
Cela peut sembler être un argument potentiel pour utiliser le modèle de référentiel, mais la principale différence ici est qu'un service est une couche plus mince et est orienté vers le retour de données entièrement cuites, plutôt que quelque chose dans lequel vous continuez à interroger, comme avec un dépôt.
Voici une prise d'Ayende Rahien: Architecting in the pit of Doom: The mal's of the repository abstraction layer
Je ne sais pas encore si je suis d'accord avec sa conclusion. C'est un piège - d'une part, si j'encapsule mon contexte EF dans des référentiels spécifiques au type avec des méthodes de récupération de données spécifiques à la requête, je suis en fait en mesure de tester mon code (en quelque sorte), ce qui est presque impossible avec Entity Cadre seul. D'un autre côté, je perds la capacité de faire des requêtes riches et la maintenance sémantique des relations (mais même lorsque j'ai un accès complet à ces fonctionnalités, j'ai toujours l'impression de marcher sur des coquilles d'oeufs autour d'EF ou de tout autre ORM que je pourrais choisir , car je ne sais jamais quelles méthodes son implémentation IQueryable peut ou ne peut pas prendre en charge, s'il interprète mon ajout à une collection de propriétés de navigation comme une création ou simplement une association, s'il va charger paresseusement ou avec impatience ou ne pas charger du tout par par défaut, etc., donc c'est peut-être pour le mieux. Le "mapping" relationnel objet à impédance zéro est quelque chose de créature mythologique - c'est peut-être pour cela que la dernière version d'Entity Framework a été nommée "Magic Unicorn").
Cependant, la récupération de vos entités via des méthodes de récupération de données spécifiques à la requête signifie que vos tests unitaires sont désormais essentiellement des tests en boîte blanche et vous n'avez pas le choix à cet égard, car vous devez savoir à l'avance exactement quelle méthode de référentiel l'unité testée va utiliser. appeler pour se moquer de lui. Et vous ne testez toujours pas les requêtes elles-mêmes, sauf si vous écrivez é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 en prétendant simplement que toutes vos entités sont des types distincts 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 les tests exactement comme vous le feriez avec une vraie base de données. Je pense à changer tous les tests de ce projet sur lequel 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ésout tous les problèmes dont je parlais plus tôt. La seule chose 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 vrai problème. Il y a un Code Project article qui parle d'utiliser Effort (par rapport à SQL CE) pour les tests.
La raison pour laquelle vous le feriez probablement est que c'est un peu redondant. Entity Framework vous offre une multitude d'avantages de codage et fonctionnels, c'est pourquoi vous l'utilisez, si vous prenez cela et l'enveloppez dans un modèle de référentiel que vous jetez ces avantages, vous pourriez tout aussi bien utiliser n'importe quelle autre couche d'accès aux données.
En théorie, je pense 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 fait valoir le lien ci-dessous, nos cadres modernes s'en occupent essentiellement maintenant.
Une très bonne raison à utiliser le modèle de référentiel est de permettre la séparation de votre logique métier et/ou de votre interface utilisateur de System.Data.Entity. Il y a de nombreux avantages à cela, y compris de réels avantages dans les tests unitaires en lui permettant d'utiliser des faux ou des faux.
Nous avons eu des problèmes avec les instances DbContext d'Entity Framework en double mais différentes lorsqu'un conteneur IoC qui crée de nouveaux référentiels () par type (par exemple, un UserRepository et une instance GroupRepository qui appellent chacun leur propre IDbSet à partir de DBContext), peut parfois provoquer plusieurs contextes par demande (dans un contexte MVC/web).
La plupart du temps, cela fonctionne toujours, mais lorsque vous ajoutez une couche de service en plus de cela et que ces services supposent que les objets créés avec un contexte seront correctement attachés en tant que collections enfants à un nouvel objet dans un autre contexte, cela échoue parfois et parfois pas t en fonction de la vitesse des commits.
Après avoir essayé le modèle de référentiel sur un petit projet, je conseille fortement de ne pas l'utiliser; non pas parce que cela complique votre système, et non parce que les données moqueuses sont un cauchemar, mais parce que vos tests deviennent inutiles !!
Les données de simulation vous permettent d'ajouter des détails sans en-têtes, d'ajouter des enregistrements qui violent 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 dernier champ de date de modification, les clés générées automatiquement, les champs calculés.
En bref, votre test sur une base de données réelle vous donne de vrais résultats et vous pouvez tester non seulement vos services et interfaces, mais aussi le comportement de la base de données. Vous pouvez vérifier si vos procédures stockées font ce qu'il faut avec les données, renvoyer le résultat attendu ou si l'enregistrement que vous avez envoyé à supprimer est vraiment supprimé! Ces tests peuvent également exposer des problèmes tels que l'oubli de générer des erreurs à partir de la procédure stockée et des milliers de scénarios de ce type.
Je pense que le cadre d'entité implémente le modèle de référentiel mieux que tous les articles que j'ai lus jusqu'à présent et cela va bien au-delà de ce qu'ils essaient d'accomplir.
Le référentiel était la meilleure pratique les jours où nous utilisions XBase, AdoX et Ado.Net, mais avec entité !! (Référentiel sur référentiel)
Enfin, je pense que trop de gens investissent beaucoup de temps dans l'apprentissage et la mise en œuvre du modèle de référentiel et refusent de le laisser partir. Surtout pour se prouver qu'ils n'ont pas perdu leur temps.