Le référentiel générique présente-t-il un réel avantage?


28

Je lisais quelques articles sur les avantages de la création de référentiels génériques pour une nouvelle application ( exemple ). L'idée semble intéressante car elle me permet d'utiliser le même référentiel pour faire plusieurs choses pour plusieurs types d'entités différents à la fois:

IRepository repo = new EfRepository(); // Would normally pass through IOC into constructor 
var c1 = new Country() { Name = "United States", CountryCode = "US" };
var c2 = new Country() { Name = "Canada", CountryCode = "CA" };
var c3 = new Country() { Name = "Mexico", CountryCode = "MX" };
var p1 = new Province() { Country = c1, Name = "Alabama", Abbreviation = "AL" };
var p2 = new Province() { Country = c1, Name = "Alaska", Abbreviation = "AK" };
var p3 = new Province() { Country = c2, Name = "Alberta", Abbreviation = "AB" };
repo.Add<Country>(c1);
repo.Add<Country>(c2);
repo.Add<Country>(c3);
repo.Add<Province>(p1);
repo.Add<Province>(p2);
repo.Add<Province>(p3);
repo.Save();

Cependant, le reste de l'implémentation du référentiel dépend fortement de Linq:

IQueryable<T> Query();
IList<T> Find(Expression<Func<T,bool>> predicate);
T Get(Expression<Func<T,bool>> predicate);
T First(Expression<Func<T,bool>> predicate);
//... and so on

Ce modèle de référentiel fonctionnait à merveille pour Entity Framework et offrait à peu près un mappage 1 à 1 des méthodes disponibles sur DbContext / DbSet. Mais compte tenu de la lente adoption de Linq sur d'autres technologies d'accès aux données en dehors d'Entity Framework, quel avantage cela offre-t-il par rapport au travail direct avec DbContext?

J'ai tenté d'écrire une version PetaPoco du référentiel, mais PetaPoco ne prend pas en charge les expressions Linq, ce qui rend la création d'une interface générique IRepository pratiquement inutile, sauf si vous ne l'utilisez que pour GetAll, GetById, Add, Update, Delete et Save de base et l'utiliser comme classe de base. Ensuite, vous devez créer des référentiels spécifiques avec des méthodes spécialisées pour gérer toutes les clauses "where" que je pouvais auparavant transmettre en tant que prédicat.

Le modèle de référentiel générique est-il utile pour quoi que ce soit en dehors de Entity Framework? Sinon, pourquoi quelqu'un l'utiliserait-il au lieu de travailler directement avec Entity Framework?


Le lien d'origine ne reflète pas le modèle que j'utilisais dans mon exemple de code. Voici un ( lien mis à jour ).



1
La question porte-t-elle vraiment sur "l'avantage d'un référentiel générique" ou est plutôt "comment cacher des requêtes complexes derrière une interface générique de référentiel"? Est-il possible d'être générique si son interface et son utilisation dépendent de linq? Notre Repositoryapi a une méthode QueryByExample qui est complètement indépendante de la technique de recherche et permettrait de décaler l'implémentation.
k3b

"Cela me permet d'utiliser le même référentiel pour faire plusieurs choses pour plusieurs types d'entités différents" => Est-ce moi ou vous venez de mal comprendre ce qu'est un référentiel générique (y compris en ce qui concerne l'article mentionné)? Pour moi, le référentiel générique a toujours signifié créer des modèles pour tous les référentiels avec une seule classe ou interface, ne pas avoir une seule instance de référentiel gérant la persistance de toutes les entités quel que soit leur type ...
guillaume31

Réponses:


35

Le référentiel générique est même inutile (et à mon humble avis également mauvais) pour Entity Framework. Il n'apporte aucune valeur supplémentaire à ce qui est déjà fourni par IDbSet<T>(qui est btw. Référentiel générique).

Comme vous l'avez déjà trouvé, l'argument selon lequel le référentiel générique peut être remplacé par une implémentation pour d'autres technologies d'accès aux données est assez faible car il peut nécessiter l'écriture de votre propre fournisseur Linq.

Le deuxième argument commun concernant les tests unitaires simplifiés est également erroné car le référentiel / ensemble moqueur avec stockage de données en mémoire remplace le fournisseur Linq par un autre qui a des capacités différentes. Le fournisseur Linq-to-entity ne prend en charge qu'un sous-ensemble de fonctionnalités Linq - il ne prend même pas en charge toutes les méthodes disponibles sur l' IQueryable<T>interface. Le partage d'arbres d'expression entre la couche d'accès aux données et les couches de logique métier empêche tout faux de la couche d'accès aux données - la logique de requête doit être séparée.

Si vous voulez avoir une abstraction "générique" forte, vous devez également impliquer d'autres modèles. Dans ce cas, vous devez utiliser un langage de requête abstrait qui peut être traduit par le référentiel dans un langage de requête spécifique pris en charge par la couche d'accès aux données utilisée. Ceci est géré par le modèle de spécification. Linq on IQueryableest une spécification (mais la traduction nécessite un fournisseur - ou un visiteur personnalisé traduisant l'arborescence d'expression en requête) mais vous pouvez définir votre propre version simplifiée et l'utiliser. Par exemple, NHibernate utilise l'API Criteria. Le moyen le plus simple reste d'utiliser un référentiel spécifique avec des méthodes spécifiques. Cette méthode est la plus simple à implémenter, la plus simple à tester et la plus simple à simuler dans les tests unitaires car la logique de requête est complètement cachée et séparée derrière l'abstraction.


Juste une question rapide, NHibernate a le ISessionqui est facilement moquable à des fins générales de test unitaire, et je serais ravi de supprimer le `` référentiel '' lorsque vous travaillez avec EF également - mais existe-t-il un moyen plus simple et direct de recréer cela? Quelque chose de semblable ISessionet ISessionFactory, parce qu'il n'y a pas IDbContextpour autant que je
sache

Non, il n'y en a pas IDbContext- si vous le souhaitez, IDbContextvous pouvez simplement en créer un et l'implémenter sur votre contexte dérivé.
Ladislav Mrnka

Vous dites donc que pour les applications MVC de tous les jours, IDbSet <T> devrait fournir un référentiel générique suffisamment bon pour oublier complètement le modèle de référentiel. Sauf dans des situations spéciales, par exemple lorsque vous n'êtes pas autorisé à utiliser de références DAL dans votre projet MVC.
ProfK

1
Bonne réponse. Certains développeurs créent simplement une autre couche d'abstractions sans aucune raison. IMO, EF n'ont pas besoin de référentiel générique ni d'une unité de travail (ils sont
intégrés

1
IDbSet <T> est un référentiel générique avec une dépendance sur l'Entity Framework qui défait en quelque sorte l'objectif d'utiliser un référentiel générique pour éliminer la dépendance sur l'Entity Framework.
Joel McBeth

7

Le problème n'est pas le modèle de référentiel. Avoir une abstraction entre obtenir des données et comment vous les obtenez est une bonne chose.

Le problème ici est la mise en œuvre. Supposer qu'une expression arbitraire fonctionnera pour le filtrage est au mieux risqué.

Faire fonctionner un référentiel pour tous vos objets directement manque un peu le point. Les objets de données sont rarement, voire jamais, directement mappés aux objets métier. Passer T pour filtrer a beaucoup moins de sens dans ces situations. Et fournir autant de fonctionnalités garantit à peu près que vous ne pourrez pas toutes les prendre en charge une fois qu'un autre fournisseur arrivera.


Il est donc plus logique d'avoir un référentiel par objet de données, ou un groupe d'objets étroitement liés, avec des méthodes spécifiques comme GetById (int id), SortedList (), etc.? Suis-je en train de renvoyer des listes d'objets de données à partir d'un référentiel ou de les transformer en objets métier avec les champs nécessaires? Je pensais que c'était ce qui s'était passé dans la couche Service / Business.
Sam

1
@sam - J'ai tendance à privilégier des référentiels plus spécifiques, oui. S'ils font de la traduction ou non, cela dépend de l'endroit où les choses sont susceptibles de changer. Si votre domaine est bien défini, je retournerais les choses sous la forme du domaine. Si les données sont bien définies, je retournerais les choses sous la forme des structures de données. Si ce n'est pas le cas, j'aurais une entité qui sert de base bien définie à partir de laquelle puis m'adapter.
Telastyn

1
Il n'y a aucune raison pour laquelle vous ne pouvez pas avoir de référentiels spécifiques qui délèguent les éléments communs à un référentiel générique, mais également des méthodes spécifiques au référentiel supplémentaires pour les appels plus complexes. Je l'ai vu de cette façon plusieurs fois.
Eric King

@EricKing J'en ai aussi, et c'était bien là-bas, mais cela a tendance à fuir une certaine abstraction, car les éléments communs ont tendance à exister uniquement en raison de la façon dont les données sont stockées (GetByID, par exemple, nécessite des tables avec des ID).
Telastyn

@Telastyn Oui, je serais d'accord avec cela, mais cela arrive aussi avec des référentiels spécifiques. Les abstractions qui fuient ne sont pas spécifiques aux référentiels génériques. (Wow, aurais-je pu formuler cela plus maladroitement?)
Eric King

2

La valeur d'une couche de données générique (un référentiel est un type particulier de couche de données) permet au code de changer le mécanisme de stockage sous-jacent avec peu ou pas d'impact sur le code appelant.

En théorie, cela fonctionne bien. En pratique, comme vous l'avez constaté, l'abstraction est souvent fuyante. Les mécanismes utilisés pour accéder aux données de l'un sont différents des mécanismes de l'autre. Dans certains cas, vous finissez par écrire le code deux fois: une fois dans la couche métier et le répéter dans la couche de données.

Le moyen le plus efficace de créer une couche de données générique est de connaître les différents types de sources de données que l'application utilisera au préalable. Comme vous l'avez vu, supposer que LINQ ou SQL est universel peut être un problème. Essayer de moderniser de nouveaux magasins de données entraînera probablement une réécriture.

[Modifier: ajouté ce qui suit.]

Cela dépend également de ce dont l'application a besoin de la couche de données. Si l'application ne fait que charger ou stocker des objets, la couche de données peut être très simple. À mesure que le besoin de rechercher, de trier et de filtrer augmente, la complexité de la couche de données augmente et les abstractions commencent à devenir fuyantes (telles que l'exposition des requêtes LINQ dans la question). Cependant, une fois que les utilisateurs peuvent fournir leurs propres requêtes, le rapport coût / bénéfice de la couche de données doit être soigneusement évalué.


1

Avoir une couche de code au-dessus de la base de données vaut la peine dans presque tous les cas. Je préférerais généralement un modèle «GetByXXXXX» dans ledit code - il vous permet d'optimiser les requêtes derrière lui selon vos besoins tout en gardant l'interface utilisateur exempte de tout encombrement d'interface de données.

Profiter des génériques est certainement un jeu équitable - avoir une Load<T>(int id)méthode a beaucoup de sens. Mais la création de référentiels autour de LINQ est l'équivalent des années 2010 de la suppression des requêtes SQL partout avec un peu plus de sécurité de type.


0

Eh bien, avec le lien fourni, je peux voir que cela peut être un wrapper pratique pour un DataServiceContext, mais ne réduit pas les manipulations de code ni améliore la lisibilité. En outre, l'accès à DataServiceQuery<T>est obstrué, ce qui limite la flexibilité de .Where()et .Single(). Aucune AddRange()alternative n'est fournie. Il n'est pas non plus Delete(Predicate)fourni qui pourrait être utile ( repo.Delete<Person>( p => p.Name=="Joe" );pour supprimer Joe-s). Etc.

Conclusion: une telle API obstrue l'API native et la limite à quelques opérations simples.


Vous avez raison. Ce n'était pas l'article utilisant le modèle que j'ai dans mon exemple de code. J'essaierai de trouver le lien quand je rentrerai.
Sam

Lien mis à jour ajouté au bas de la question.
Sam

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.