J'ai emprunté de nombreux chemins et créé de nombreuses implémentations de référentiels sur différents projets et ... j'ai jeté l'éponge et abandonné, voici pourquoi.
Codage de l'exception
Codez-vous pour 1% de chances que votre base de données passe d'une technologie à une autre? Si vous pensez à l'état futur de votre entreprise et que vous dites oui c'est une possibilité, alors a) ils doivent avoir beaucoup d'argent pour se permettre de faire une migration vers une autre technologie DB ou b) vous choisissez une technologie DB pour le plaisir ou c ) quelque chose a terriblement mal tourné avec la première technologie que vous avez décidé d'utiliser.
Pourquoi jeter la riche syntaxe LINQ?
LINQ et EF ont été développés pour que vous puissiez faire des choses intéressantes avec lui pour lire et parcourir des graphiques d'objets. Créer et maintenir un référentiel qui peut vous donner la même flexibilité pour le faire est une tâche monstrueuse. D'après mon expérience, chaque fois que j'ai créé un référentiel, j'ai TOUJOURS eu une fuite de logique métier dans la couche du référentiel pour rendre les requêtes plus performantes et / ou réduire le nombre de hits dans la base de données.
Je ne veux pas créer une méthode pour chaque permutation d'une requête que je dois écrire. Je pourrais aussi bien écrire des procédures stockées. Je ne veux pas GetOrder
, GetOrderWithOrderItem
, GetOrderWithOrderItemWithOrderActivity
, GetOrderByUserId
et ainsi de suite ... Je veux juste l'entité principale et traverse le graphique et inclure des objets que je donc s'il vous plaît.
La plupart des exemples de référentiels sont des conneries
À moins que vous ne développiez quelque chose de VRAIMENT dépouillé comme un blog ou quelque chose, vos requêtes ne seront jamais aussi simples que 90% des exemples que vous trouvez sur Internet autour du modèle de référentiel. Je ne peux insister assez sur ce point! C'est quelque chose qu'il faut ramper dans la boue pour comprendre. Il y aura toujours cette requête qui casse votre référentiel / solution parfaitement pensé que vous avez créé, et ce n'est qu'à ce moment-là que vous vous remettez en question et que la dette / l'érosion technique commence.
Ne me testez pas unitaire, frère
Mais qu'en est-il des tests unitaires si je n'ai pas de référentiel? Comment vais-je me moquer? Simple, vous ne le faites pas. Regardons-le sous les deux angles:
Pas de référentiel - Vous pouvez vous moquer de l' DbContext
utilisation d'une IDbContext
ou d'autres astuces, mais vous testez vraiment LINQ to Objects et non LINQ to Entities car la requête est déterminée au moment de l'exécution ... OK donc ce n'est pas bon! C'est donc maintenant au test d'intégration de couvrir cela.
Avec référentiel - Vous pouvez désormais simuler vos référentiels et tester unitaire la ou les couches intermédiaires. Super non? Eh bien pas vraiment ... Dans les cas ci-dessus où vous devez laisser passer de la logique dans la couche du référentiel pour rendre les requêtes plus performantes et / ou moins heureuses dans la base de données, comment vos tests unitaires peuvent-ils couvrir cela? C'est maintenant dans la couche repo et vous ne voulez pas tester, n'est-ce pas IQueryable<T>
? Soyons également honnêtes, vos tests unitaires ne couvriront pas les requêtes qui ont une .Where()
clause de 20 lignes et.Include()
'un tas de relations et frappe à nouveau la base de données pour faire toutes ces autres choses, bla, bla, bla de toute façon parce que la requête est générée au moment de l'exécution. De plus, puisque vous avez créé un référentiel pour ignorer la persistance des couches supérieures, si vous souhaitez maintenant changer la technologie de votre base de données, désolé, vos tests unitaires ne garantiront certainement pas les mêmes résultats lors de l'exécution, revenons aux tests d'intégration. Donc, tout l'intérêt du référentiel semble étrange.
2 cents
Nous perdons déjà beaucoup de fonctionnalités et de syntaxe lors de l'utilisation d'EF sur des procédures stockées simples (insertions en bloc, suppressions en bloc, CTE, etc.) mais je code également en C # pour ne pas avoir à taper binaire. Nous utilisons EF afin que nous puissions avoir la possibilité d'utiliser différents fournisseurs et de travailler avec des graphes d'objets d'une manière bien liée entre beaucoup de choses. Certaines abstractions sont utiles et d'autres non.
Coding for the exception
: L'utilisation de référentiels ne permet pas de changer de moteur de base de données. Il s'agit de séparer les affaires de la persévérance.DbSet
est le référentiel etDbContext
est l' unité de travail . Pourquoi implémenter un modèle de référentiel alors que ORM le fait déjà pour nous! Pour les tests, changez simplement le fournisseur enInMemory
. Et faites vos tests! Il est bien documenté dans MSDN.Le modèle de référentiel est une abstraction . Son but est de réduire la complexité et de rendre le reste du code persistant ignorant. En prime, il vous permet d'écrire des tests unitaires au lieu de tests d' intégration .
Le problème est que de nombreux développeurs ne parviennent pas à comprendre le but des modèles et créent des référentiels qui divulguent des informations spécifiques à la persistance jusqu'à l'appelant (généralement en exposant
IQueryable<T>
). Ce faisant, ils n'obtiennent aucun avantage par rapport à l'utilisation directe de l'OR / M.Mettre à jour pour répondre à une autre réponse
Codage de l'exception
Utiliser des référentiels ne consiste pas à pouvoir changer de technologie de persistance (c'est-à-dire changer de base de données ou utiliser un service Web, etc.). Il s'agit de séparer la logique métier de la persistance pour réduire la complexité et le couplage.
Tests unitaires vs tests d'intégration
Vous n'écrivez pas de tests unitaires pour les référentiels. période.
Mais en introduisant des référentiels (ou toute autre couche d'abstraction entre la persistance et l'entreprise), vous êtes en mesure d'écrire des tests unitaires pour la logique métier. c'est-à-dire que vous n'avez pas à vous soucier de l'échec de vos tests en raison d'une base de données mal configurée.
Quant aux requêtes. Si vous utilisez LINQ, vous devez également vous assurer que vos requêtes fonctionnent, tout comme vous avez à faire avec les référentiels. et cela se fait à l'aide de tests d'intégration.
La différence est que si vous n'avez pas mélangé votre entreprise avec des instructions LINQ, vous pouvez être sûr à 100% que c'est votre code de persistance qui échoue et pas autre chose.
Si vous analysez vos tests, vous verrez également qu'ils sont beaucoup plus propres si vous n'avez pas de préoccupations mitigées (c'est-à-dire LINQ + Business logic)
Exemples de référentiel
La plupart des exemples sont des conneries. c'est très vrai. Cependant, si vous recherchez un modèle de conception sur Google, vous trouverez de nombreux exemples merdiques. Ce n'est pas une raison pour éviter d'utiliser un motif.
Construire une implémentation correcte du référentiel est très simple. En fait, vous n'avez qu'à suivre une seule règle:
N'ajoutez rien dans la classe de référentiel jusqu'au moment même où vous en avez besoin
De nombreux codeurs sont paresseux et essaient de créer un référentiel générique et d'utiliser une classe de base avec de nombreuses méthodes dont ils pourraient avoir besoin. YAGNI. Vous écrivez la classe du référentiel une fois et la conservez aussi longtemps que l'application dure (peut être des années). Pourquoi foutre le bordel en étant paresseux. Gardez-le propre sans aucun héritage de classe de base. Cela le rendra beaucoup plus facile à lire et à maintenir.
(La déclaration ci-dessus est une directive et non une loi. Une classe de base peut très bien être motivée. Réfléchissez simplement avant de l'ajouter, afin de l'ajouter pour les bonnes raisons)
Vieilles affaires
Conclusion:
Si cela ne vous dérange pas d'avoir des instructions LINQ dans votre code métier ni de vous soucier des tests unitaires, je ne vois aucune raison de ne pas utiliser Entity Framework directement.
Mettre à jour
J'ai blogué à la fois sur le modèle de référentiel et sur ce que signifie vraiment «abstraction»: http://blog.gauffin.org/2013/01/repository-pattern-done-right/
Mise à jour 2
Encore une fois: une entité avec plus de 20 champs n'est pas correctement modélisée. C'est une entité DIEU. Décomposez-le.
Je ne dis pas que ce
IQueryable
n'était pas fait pour interroger. Je dis que ce n'est pas bon pour une couche d'abstraction comme le modèle de référentiel car il fuit. Il n'y a pas de fournisseur LINQ To Sql complet à 100% (comme EF).Ils ont tous des éléments spécifiques à l'implémentation, comme comment utiliser le chargement hâtif / lazy ou comment faire des instructions SQL "IN". L'exposition
IQueryable
dans le référentiel oblige l'utilisateur à connaître toutes ces choses. Ainsi, toute tentative d'abstraction de la source de données est un échec complet. Vous ajoutez simplement de la complexité sans obtenir aucun avantage par rapport à l'utilisation directe de l'OR / M.Soit implémentez correctement le modèle de référentiel, soit ne l'utilisez pas du tout.
(Si vous voulez vraiment gérer de grandes entités, vous pouvez combiner le modèle de référentiel avec le modèle de spécification . Cela vous donne une abstraction complète qui est également testable.)
la source
L'
Repository
abstraction et l'UnitOfWork
abstraction de l' OMI ont une place très précieuse dans tout développement significatif. Les gens discuteront des détails de la mise en œuvre, mais tout comme il existe de nombreuses façons d'écorcher un chat, il existe de nombreuses façons d'implémenter une abstraction.Votre question est spécifiquement d'utiliser ou de ne pas utiliser et pourquoi.
Comme vous avez sans doute réalisé que vous avez déjà ces deux modèles intégrés dans Entity Framework,
DbContext
est leUnitOfWork
etDbSet
est leRepository
. Vous n'avez généralement pas besoin de tester l'unitéUnitOfWork
ouRepository
eux - mêmes car ils facilitent simplement entre vos classes et les implémentations d'accès aux données sous-jacentes. Ce que vous devrez faire, encore et encore, c'est vous moquer de ces deux abstractions lors du test unitaire de la logique de vos services.Vous pouvez vous moquer, truquer ou autre chose avec des bibliothèques externes en ajoutant des couches de dépendances de code (que vous ne contrôlez pas) entre la logique faisant le test et la logique testée.
Donc, un point mineur est que d' avoir votre propre abstraction pour
UnitOfWork
etRepository
vous donne un maximum de contrôle et de flexibilité lorsque vous vous moquez de vos tests unitaires.Très bien, mais pour moi, le vrai pouvoir de ces abstractions est qu'elles fournissent un moyen simple d'appliquer les techniques de programmation orientée aspect et d'adhérer aux principes SOLID .
Donc vous avez votre
IRepository
:Et sa mise en œuvre:
Rien d'extraordinaire jusqu'à présent, mais maintenant nous voulons ajouter un peu de journalisation - facile avec un décorateur de journalisation .
Tout est fait et sans modification de notre code existant . Il existe de nombreuses autres préoccupations transversales que nous pouvons ajouter, telles que la gestion des exceptions, la mise en cache des données, la validation des données ou autre et tout au long de notre processus de conception et de construction, la chose la plus précieuse que nous ayons qui nous permet d'ajouter des fonctionnalités simples sans changer aucun de notre code existant. est notre
IRepository
abstraction .Maintenant, j'ai souvent vu cette question sur StackOverflow - «comment faire fonctionner Entity Framework dans un environnement multi-locataire?».
https://stackoverflow.com/search?q=%5Bentity-framework%5D+multi+tenant
Si vous avez une
Repository
abstraction, la réponse est "c'est facile d'ajouter un décorateur"IMO, vous devez toujours placer une simple abstraction sur tout composant tiers qui sera référencé dans plus d'une poignée d'endroits. De ce point de vue, un ORM est le candidat parfait car il est référencé dans une grande partie de notre code.
La réponse qui me vient normalement à l'esprit lorsque quelqu'un dit «pourquoi devrais-je avoir une abstraction (par exemple
Repository
) sur telle ou telle bibliothèque tierce» est «pourquoi pas vous?»Les décorateurs PS sont extrêmement simples à appliquer à l'aide d'un conteneur IoC, tel que SimpleInjector .
la source
Tout d'abord, comme suggéré par une réponse, EF lui-même est un modèle de référentiel, il n'est pas nécessaire de créer une abstraction supplémentaire juste pour le nommer comme référentiel.
Référentiel moquable pour les tests unitaires, en avons-nous vraiment besoin?
Nous laissons EF communiquer pour tester DB dans des tests unitaires afin de tester notre logique métier directement par rapport à SQL test DB. Je ne vois aucun avantage à se moquer d'un modèle de référentiel. Quel est vraiment le problème lors des tests unitaires sur la base de données de test? Comme il s'agit d'opérations en bloc ne sont pas possibles et nous finissons par écrire du SQL brut. SQLite en mémoire est le candidat idéal pour effectuer des tests unitaires sur une base de données réelle.
Abstraction inutile
Voulez-vous créer un référentiel juste pour qu'à l'avenir vous puissiez facilement remplacer EF par NHbibernate etc. ou autre chose? Ça a l'air génial, mais est-ce vraiment rentable?
Linq tue les tests unitaires?
J'aimerai voir des exemples sur la façon dont cela peut tuer.
Injection de dépendances, IoC
Wow, ce sont de bons mots, bien sûr qu'ils ont fière allure en théorie, mais parfois vous devez choisir un compromis entre un excellent design et une excellente solution. Nous avons utilisé tout cela, et nous avons fini par tout jeter à la poubelle et choisir une approche différente. La taille par rapport à la vitesse (taille du code et vitesse de développement) compte énormément dans la vie réelle. Les utilisateurs ont besoin de flexibilité, ils ne se soucient pas de savoir si votre code est excellent en termes de conception DI ou IoC.
Sauf si vous créez Visual Studio
Toutes ces excellentes conceptions sont nécessaires si vous créez un programme complexe comme Visual Studio ou Eclipse qui sera développé par de nombreuses personnes et qui doit être hautement personnalisable. Tous les grands modèles de développement sont apparus après des années de développement que ces IDE ont traversées, et ils ont évolué à un endroit où tous ces grands modèles de conception sont si importants. Mais si vous effectuez une paie Web simple ou une application d'entreprise simple, il est préférable que vous évoluiez dans votre développement avec le temps, au lieu de passer du temps à le créer pour des millions d'utilisateurs où il ne sera déployé que pour des centaines d'utilisateurs.
Référentiel en tant que vue filtrée - ISecureRepository
De l'autre côté, le référentiel doit être une vue filtrée d'EF qui protège l'accès aux données en appliquant le remplissage nécessaire en fonction de l'utilisateur / rôle actuel.
Mais cela complique encore plus le référentiel car il se retrouve dans une énorme base de code à maintenir. Les gens finissent par créer différents référentiels pour différents types d'utilisateurs ou une combinaison de types d'entités. Non seulement cela, nous nous retrouvons également avec beaucoup de DTO.
La réponse suivante est un exemple d'implémentation de Filtered Repository sans créer un ensemble complet de classes et de méthodes. Il peut ne pas répondre directement à la question, mais il peut être utile pour en dériver une.
Clause de non-responsabilité: Je suis l'auteur du SDK Entity REST.
http://entityrestsdk.codeplex.com
En gardant à l'esprit ci-dessus, nous avons développé un SDK qui crée un référentiel de vues filtrées basé sur SecurityContext qui contient des filtres pour les opérations CRUD. Et seuls deux types de règles simplifient les opérations complexes. Le premier est l'accès à l'entité, et l'autre est la règle de lecture / écriture pour la propriété.
L'avantage est que vous ne réécrivez pas la logique métier ou les référentiels pour différents types d'utilisateurs, il vous suffit simplement de bloquer ou de leur accorder l'accès.
Ces règles LINQ sont évaluées par rapport à Database dans la méthode SaveChanges pour chaque opération, et ces règles agissent comme pare-feu devant Database.
la source
Il y a beaucoup de débats sur la méthode correcte, donc je considère que les deux sont acceptables, donc j'utilise toujours celle que j'aime le plus (qui n'est pas un référentiel, UoW).
Dans EF, UoW est implémenté via DbContext et les DbSets sont des référentiels.
Quant à savoir comment travailler avec la couche de données, je travaille directement sur l'objet DbContext, pour les requêtes complexes, je créerai des méthodes d'extension pour la requête qui peuvent être réutilisées.
Je crois qu'Ayende a également quelques articles sur la façon dont l'abstraction des opérations CUD est mauvaise.
Je crée toujours une interface et mon contexte en hérite afin de pouvoir utiliser un conteneur IoC pour DI.
la source
Ce qui s'applique le plus sur EF n'est pas un modèle de référentiel. Il s'agit d'un modèle de façade (abstraction des appels aux méthodes EF dans des versions plus simples et plus faciles à utiliser).
EF est celui qui applique le modèle de référentiel (ainsi que le modèle d'unité de travail). Autrement dit, EF est celui qui fait abstraction de la couche d'accès aux données afin que l'utilisateur n'ait aucune idée qu'il a affaire à SQLServer.
Et à cela, la plupart des «référentiels» sur EF ne sont même pas de bonnes Façades car ils se contentent de mapper, assez simplement, à des méthodes uniques dans EF, au point même d'avoir les mêmes signatures.
Les deux raisons, alors, pour appliquer ce soi-disant modèle "Repository" sur EF est de permettre des tests plus faciles et d'établir un sous-ensemble d'appels "en conserve". Pas mal en eux-mêmes, mais clairement pas un référentiel.
la source
Linq est aujourd'hui un «référentiel».
ISession + Linq est déjà le référentiel, et vous n'avez besoin ni de
GetXByY
méthodes ni deQueryData(Query q)
généralisation. Étant un peu paranoïaque à l'utilisation de DAL, je préfère toujours l'interface du référentiel. (Du point de vue de la maintenabilité, nous devons également avoir une certaine façade sur des interfaces d'accès aux données spécifiques).Voici le référentiel que nous utilisons - il nous dissocie de l'utilisation directe de nhibernate, mais fournit une interface linq (comme un accès ISession dans des cas exceptionnels, qui sont éventuellement sujets à refactor).
la source
Le référentiel (ou comment on choisit de l'appeler) à ce moment pour moi consiste principalement à faire abstraction de la couche de persistance.
Je l'utilise couplé à des objets de requête donc je n'ai pas de couplage avec une technologie particulière dans mes applications. Et cela facilite également beaucoup les tests.
Donc, j'ai tendance à avoir
Ajoutez éventuellement des méthodes asynchrones avec des rappels en tant que délégués. Le dépôt est facile à implémenter de manière générique , je suis donc en mesure de ne pas toucher une ligne de l'implémentation d'une application à l'autre. Eh bien, c'est vrai au moins lors de l'utilisation de NH, je l'ai fait aussi avec EF, mais m'a fait détester EF. 4. La conversation est le début d'une transaction. Très cool si quelques classes partagent l'instance du référentiel. De plus, pour NH, un dépôt dans mon implémentation équivaut à une session qui est ouverte à la première demande.
Puis les objets de requête
Pour la configuration, j'utilise dans NH uniquement pour passer dans l'ISession. En EF n'a plus ou moins de sens.
Un exemple de requête serait .. (NH)
Pour faire une requête EF, vous devez avoir le contexte dans la base abstraite, pas dans la session. Mais bien sûr, le ifc serait le même.
De cette manière, les requêtes sont elles-mêmes encapsulées et facilement testables. Mieux encore, mon code ne repose que sur des interfaces. Tout est très propre. Les objets de domaine (métier) ne sont que cela, par exemple, il n'y a pas de mélange de responsabilités comme lors de l'utilisation du modèle d'enregistrement actif qui est difficilement testable et mélange le code d'accès aux données (requête) dans l'objet de domaine et, ce faisant, mélange les préoccupations (objet qui récupère lui-même ??). Tout le monde est toujours libre de créer des POCO pour le transfert de données.
Dans l'ensemble, cette approche apporte beaucoup de réutilisation et de simplicité du code au détriment de rien que je puisse imaginer. Des idées?
Et merci beaucoup à Ayende pour ses excellents messages et son dévouement continu. C'est ses idées ici (objet de requête), pas les miennes.
la source
Pour moi, c'est une décision simple, avec relativement peu de facteurs. Les facteurs sont:
Donc, si mon application ne peut pas justifier le n ° 2, séparer les modèles de domaine et de données, je ne me soucierai généralement pas du n ° 5.
la source