Pourquoi ne devrais-je pas utiliser le modèle de référentiel avec Entity Framework?

203

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?

StringBuilder
la source
60
C'était une question
piège
2
J'aurais probablement répondu à l'intervieweur que Microsoft utilisait très souvent le modèle de référentiel pendant la démonstration du cadre de l'entité: | .
Laurent Bourgault-Roy
1
Alors, quelle était la raison de l'intervieweur pour que ce ne soit pas une bonne idée?
Bob Horn
3
Le fait amusant est que la recherche de "modèle de référentiel" dans Google donne les résultats qui sont principalement liés à Entity Framework et explique comment utiliser le modèle avec EF.
Arseni Mourzenko
2
Consultez le blog de ayende ayende.com/blog . D'après ce que je sais, il utilisait le modèle Repository, mais l'a finalement abandonné au profit du modèle d'objet de requête
Jaime Sangcap.

Réponses:

99

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 DbContextavec un référentiel est la testabilité par unité . Vous pouvez avoir votre IRepositoryinterface qui a 2 implémentations, une (le référentiel réel) qui utilise DbContextpour parler à la base de données et la seconde, FakeRepositoryqui peut renvoyer des objets en mémoire / des données fictives. Cela rend votre IRepositoryunité 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
  }    
}
Shyju
la source
3
Je n'ai pas dit que cela ne fonctionnerait pas, je travaille également avec un modèle de référentiel avec EF, mais aujourd'hui, on m'a demandé pourquoi IL N'EST PAS BON d'utiliser ce modèle avec DataBase, une application utilisant Database
2
Ok, puisque c'est la réponse la plus populaire, je l'ai choisie comme réponse correcte
65
Quand était la dernière fois que le plus populaire == correct?
HDave
14
DbContext est déjà un référentiel, le référentiel est censé être une abstraction de bas niveau. Si vous souhaitez résumer différentes sources de données, créez des objets pour les représenter.
Daniel Little
7
ColacX. Nous avons juste essayé cela - DBcontext dans la couche contrôleur - et nous revenons au modèle de prise en pension. Avec le modèle Repo, les tests unitaires sont passés de moquages ​​massifs de DbContext qui échouaient constamment. EF était difficile à utiliser et fragile et coûtait des heures de recherche pour les nuances de EF. Nous avons maintenant de petites simulacres simples du rapport. Le code est plus propre. La séparation du travail est plus claire. Je ne suis plus d'accord avec la foule sur le fait qu'EF est déjà un modèle de prise en pension et déjà testable par l'unité.
Rhyous
435

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. DbContextest votre unité de travail (UoW) et chacun DbSetest 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.

Chris Pratt
la source
68
Cela semble être la seule réponse correcte.
Mike Chamberlain
10
Vous pouvez vous moquer DbContextde EF6 + (voir: msdn.microsoft.com/en-us/data/dn314429.aspx ). Même dans les versions moins, vous pouvez utiliser une fausse DbContextclasse -comme avec moquaient DbSets, car DbSetmet en œuvre un iterface, IDbSet.
Chris Pratt
14
@ TheZenker, vous n'avez peut-être pas suivi exactement le modèle de référentiel. La différence la plus stricte est la valeur de retour. Les référentiels renvoient des objets interrogables, alors que les services devraient renvoyer des éléments énumérables. Même ce n'est pas vraiment noir et blanc, car il y a un certain chevauchement. C'est plus dans la façon dont vous l'utilisez. Un référentiel doit simplement renvoyer l'ensemble de tous les objets, sur lesquels vous allez ensuite interroger, tandis que le service devrait renvoyer l'ensemble de données final et ne devrait pas prendre en charge d'autres interrogations.
Chris Pratt
10
Au risque de paraître égoïste: ils ont tort. Pour ce qui est des didacticiels officiels, Microsoft a utilisé des référentiels à partir de ce que j'ai vu depuis EF6. En ce qui concerne le livre, je ne peux pas expliquer pourquoi l'auteur a choisi d'utiliser des référentiels. Ce que je peux dire, en tant que membre des tranchées créant des applications à grande échelle, est que l’utilisation du modèle de référentiel avec Entity Framework est un cauchemar de maintenance. Une fois que vous passez à quelque chose de plus complexe qu'une poignée de référentiels, vous finissez par consacrer des quantités de temps exhorbitantes à la gestion de vos référentiels / unité de travail.
Chris Pratt
6
Je n'ai généralement qu'un service par base de données ou méthode d'accès. J'utilise des méthodes génériques pour interroger plusieurs types d'entités à partir du même ensemble de méthodes. J'utilise Ninject pour injecter mon contexte dans mon service, puis mon service dans mes contrôleurs, pour que tout soit propre et ordonné.
Chris Pratt
45

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.

Luksan
la source
3
Tout article d’architecture sans mention de test unitaire est automatiquement envoyé à la corbeille pour moi. L’un des objectifs du modèle de référentiel est d’obtenir une certaine capacité de test.
Sleeper Smith
3
Vous pouvez toujours avoir des tests unitaires sans envelopper le contexte EF (qui est déjà un référentiel). Vous devriez être en train de tester votre domaine / services et non les requêtes de base de données (ce sont des tests d'intégration).
Daniel Little
2
La testabilité d'EF s'est grandement améliorée dans la version 6. Vous pouvez maintenant vous moquer complètement 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. DbContextn’est guère plus qu’une classe pour héberger vos DbSetproprié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.
Chris Pratt
Perdre la navigation dans les entités associées est mauvais et va à l’encontre de la POO, mais vous aurez plus de contrôle sur ce qui est interrogé.
Alireza
Au point de tester, EF Core a parcouru un long chemin avec les solutions In-Memory et In-Memory avec les fournisseurs Sqlite prêts à l'emploi pour permettre les tests unitaires. Utilisez docker lorsque vous avez besoin de tests d'intégration pour exécuter des tests sur une base de données conteneurisée.
Sudhanshu Mishra
16

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.

Neuf queues
la source
Pourriez-vous s'il vous plaît dire quelques-uns des avantages pour "Entity Framework vous donne une richesse d'avantages de codage et fonctionnels"?
ManirajSS
2
c'est ce qu'il voulait dire. var id = Entity.Where (i => i.Id == 1337) .Single () encapsulé et encapsulé dans un référentiel auquel vous ne pouvez pas appliquer la logique de requête de ce type depuis l'extérieur, ce qui vous oblige à ajouter du code supplémentaire à le référentiel et l'interface pour récupérer l'id. B renvoie le contexte de l'entité à partir du référentiel afin que vous puissiez écrire la logique de requête (ce qui est tout simplement absurde)
ColacX
14

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.

Reconsidérer le modèle de référentiel

Splendeur
la source
J'ai aimé l'article, mais à mon humble avis pour les applications d'entreprise, la couche d'abstraction entre DAL et Bl DOIT avoir la fonctionnalité, car vous ne pouvez pas savoir exactement ce qui sera utilisé demain. Mais merci de partager le lien
1
Personnellement, je pense que c’est vrai, par exemple, pour NHibernate ( ISessionFactoryet que l’ ISessionon se moque facilement de lui), mais ce n’est pas si facile avec DbContext, malheureusement ...
Patryk Ćwiek
6

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.

James Culshaw
la source
Je suis d'accord avec cette réponse. Mes référentiels sont essentiellement des méthodes d'extension, qui ne font que construire des arbres d'expression. Sur une abstraction TRÈS simple qui fournit simplement des fonctionnalités génériques directement au-dessus de dbcontext. Le seul objectif réel de l’abstraction est de rendre IoC un peu plus facile. Je pense que les gens essaient de faire des choses dans leurs dépôts qu'ils ne devraient pas faire. Ils effectuent un repo par entité ou y insèrent une logique métier qui devrait se trouver dans la couche services. Vous n'avez en fait besoin que d'un seul dépôt générique simple. Ce n'est pas nécessaire, fournit simplement une interface cohérente.
Brandon
Une dernière chose que je voulais juste ajouter. Oui, le CQRS est une méthodologie très supérieure dans la plupart des cas. Pour certains clients auxquels j'ai travaillé, lorsque les responsables de la base de données ne travaillent pas bien avec les développeurs (ce qui se produit plus souvent qu'on ne le pense surtout dans les banques), EF sur SQL est la meilleure option. Dans ce scénario spécifique, lorsque vous n'avez absolument aucun contrôle sur votre base de données, la structure du référentiel a du sens. Parce que ressemble beaucoup à la structure de données et qu'il est facile de traduire ce qui se passe dans la base de données et inversement. C'est vraiment une décision politique et logistique à mon avis. Pour apaiser les dieux de la DB.
Brandon
1
Je commence en fait à remettre en question mes opinions antérieures à ce sujet. EF est un modèle combiné d'unité de travail et de référentiel. Comme Chris Pratt mentionné ci-dessus avec EF6, vous pouvez facilement vous moquer des objets Context et DbSet. Je continue de penser que l'accès aux données doit être encapsulé dans des classes afin de protéger les classes de logique métier du mécanisme d'accès aux données, mais aller de l'avant et encapsuler EF avec un autre référentiel et l'abstraction d'Unité de travail semble excessif.
James Culshaw
Je ne pense pas que ce soit une bonne réponse, car votre argument de base est simplement qu’il existe de nombreux avantages et que vous n’en listez qu’un. La liste que vous faites n’est pas une bonne raison, car vous pouvez utiliser une base de données en mémoire pour que les entités puissent le faire. tests unitaires.
Joel McBeth
@ jcmcbeth Si vous regardez mon commentaire directement au-dessus de votre, vous verrez que j'ai changé d'avis initial concernant le modèle de référentiel et EF.
James Culshaw
0

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.

Mufasa
la source
J'ai rencontré ce problème dans plusieurs projets différents.
ColacX
0

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.

Zawer Haroon
la source
1
Sauf que vous ne voulez PAS tester le comportement de votre base de données lors de tests unitaires, car ce n'est pas du tout ce niveau de test.
Mariusz Jamro
Oui, ce dont vous parlez, ce sont des tests d’ intégration , certes utiles, mais les tests unitaires sont totalement différents. Vos tests unitaires ne doivent jamais toucher une vraie base de données, mais vous êtes encouragés à ajouter des tests d'intégration.
Chris Pratt
-3

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!

Tonnerre
la source