web-dev-qa-db-fra.com

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

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 agréable 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 essayé 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 presque inutile, sauf si vous ne l'utilisez que pour les fonctions de base GetAll, GetById, Add , Mettre à jour, Supprimer et Enregistrer les méthodes et les 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 ).

28
Sam

Le référentiel générique est même inutile (et à mon humble avis aussi 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 au sujet des tests unitaires simplifiés est également faux parce que 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 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 sur IQueryable est une spécification (mais la traduction requiert un fournisseur - ou un visiteur personnalisé traduisant l'arborescence des expressions 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.

36
Ladislav Mrnka

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.

7
Telastyn

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 coût/avantage de la couche de données doit être soigneusement pesé.

2
akton

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 d'encombrement de l'interface de données.

Profiter des génériques est certainement un jeu équitable - avoir une méthode Load<T>(int id) 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 de requêtes SQL partout avec un peu plus de sécurité.

1
Wyatt Barnett

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é. De plus, l'accès à DataServiceQuery<T> Est obstrué, limitant la flexibilité à .Where() et .Single(). AddRange() ou des alternatives ne sont pas non plus fournies. Delete(Predicate) n'est pas non plus fourni, ce 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.

0
aiodintsov