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 ).
Réponses:
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
IQueryable
est 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.la source
ISession
qui 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 semblableISession
etISessionFactory
, parce qu'il n'y a pasIDbContext
pour autant que jeIDbContext
- si vous le souhaitez,IDbContext
vous pouvez simplement en créer un et l'implémenter sur votre contexte dérivé.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.
la source
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é.
la source
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.la source
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()
. AucuneAddRange()
alternative n'est fournie. Il n'est pas non plusDelete(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.
la source
Je dirais "oui". En fonction de votre modèle de données, un référentiel générique bien développé peut rendre le niveau d'accès aux données plus léger, plus net et beaucoup plus robuste.
Lisez cette série d'articles (par @ chris-pratt ):
la source