Nous développons une application ASP.NET MVC et construisons maintenant les classes de référentiel / service. Je me demande s'il y a des avantages majeurs à créer une interface IRepository générique que tous les référentiels implémentent, par rapport à chaque référentiel ayant sa propre interface et un ensemble de méthodes uniques.
Par exemple: une interface générique IRepository pourrait ressembler à (tirée de cette réponse ):
public interface IRepository : IDisposable
{
T[] GetAll<T>();
T[] GetAll<T>(Expression<Func<T, bool>> filter);
T GetSingle<T>(Expression<Func<T, bool>> filter);
T GetSingle<T>(Expression<Func<T, bool>> filter, List<Expression<Func<T, object>>> subSelectors);
void Delete<T>(T entity);
void Add<T>(T entity);
int SaveChanges();
DbTransaction BeginTransaction();
}
Chaque référentiel implémenterait cette interface, par exemple:
- CustomerRepository: IRepository
- ProductRepository: IRepository
- etc.
L'alternative que nous avons suivie dans les projets précédents serait:
public interface IInvoiceRepository : IDisposable
{
EntityCollection<InvoiceEntity> GetAllInvoices(int accountId);
EntityCollection<InvoiceEntity> GetAllInvoices(DateTime theDate);
InvoiceEntity GetSingleInvoice(int id, bool doFetchRelated);
InvoiceEntity GetSingleInvoice(DateTime invoiceDate, int accountId); //unique
InvoiceEntity CreateInvoice();
InvoiceLineEntity CreateInvoiceLine();
void SaveChanges(InvoiceEntity); //handles inserts or updates
void DeleteInvoice(InvoiceEntity);
void DeleteInvoiceLine(InvoiceLineEntity);
}
Dans le second cas, les expressions (LINQ ou autre) seraient entièrement contenues dans l'implémentation du référentiel, quiconque implémente le service a juste besoin de savoir quelle fonction de référentiel appeler.
Je suppose que je ne vois pas l'avantage d'écrire toute la syntaxe d'expression dans la classe de service et de la transmettre au référentiel. Cela ne signifierait-il pas que du code LINQ facile à manipuler est dupliqué dans de nombreux cas?
Par exemple, dans notre ancien système de facturation, nous appelons
InvoiceRepository.GetSingleInvoice(DateTime invoiceDate, int accountId)
à partir de quelques services différents (client, facture, compte, etc.). Cela semble beaucoup plus propre que d'écrire ce qui suit à plusieurs endroits:
rep.GetSingle(x => x.AccountId = someId && x.InvoiceDate = someDate.Date);
Le seul inconvénient que je vois à l'utilisation de l'approche spécifique est que nous pourrions nous retrouver avec de nombreuses permutations de fonctions Get *, mais cela semble toujours préférable à pousser la logique de l'expression dans les classes Service.
Qu'est-ce que je rate?
la source
Réponses:
Il s'agit d'un problème aussi ancien que le modèle de référentiel lui-même. L'introduction récente de LINQ
IQueryable
, une représentation uniforme d'une requête, a provoqué de nombreuses discussions sur ce sujet même.Je préfère moi-même les référentiels spécifiques, après avoir travaillé très dur pour construire un framework de référentiel générique. Peu importe le mécanisme intelligent que j'ai essayé, je me suis toujours retrouvé au même problème: un référentiel fait partie du domaine en cours de modélisation, et ce domaine n'est pas générique. Toutes les entités ne peuvent pas être supprimées, toutes les entités ne peuvent pas être ajoutées, toutes les entités ne disposent pas d'un référentiel. Les requêtes varient énormément; l'API du référentiel devient aussi unique que l'entité elle-même.
Un modèle que j'utilise souvent est d'avoir des interfaces de référentiel spécifiques, mais une classe de base pour les implémentations. Par exemple, en utilisant LINQ to SQL, vous pouvez faire:
Remplacez-la
DataContext
par l'unité de travail de votre choix. Un exemple de mise en œuvre pourrait être:Notez que l'API publique du référentiel n'autorise pas la suppression des utilisateurs. En outre, exposer
IQueryable
est une toute autre boîte de vers - il y a autant d'opinions que de nombril sur ce sujet.la source
Je suis en fait légèrement en désaccord avec le message de Bryan. Je pense qu'il a raison, qu'en fin de compte, tout est très unique et ainsi de suite. Mais en même temps, la plupart de cela sort lorsque vous concevez, et je trouve que la création d'un référentiel générique et son utilisation lors du développement de mon modèle, je peux créer une application très rapidement, puis la refactoriser pour une plus grande spécificité à mesure que je trouve le besoin de le faire.
Donc, dans des cas comme celui-là, j'ai souvent créé un IRepository générique qui a la pile CRUD complète, et qui me permet de jouer rapidement avec l'API et de laisser les gens jouer avec l'interface utilisateur et faire des tests d'intégration et d'acceptation des utilisateurs en parallèle. Ensuite, comme je trouve que j'ai besoin de requêtes spécifiques sur le dépôt, etc., je commence à remplacer cette dépendance par la dépendance spécifique si nécessaire et à partir de là. Un impl sous-jacent. est facile à créer et à utiliser (et peut-être accroché à une base de données en mémoire ou à des objets statiques ou des objets simulés ou autre).
Cela dit, ce que j'ai commencé à faire ces derniers temps, c'est de briser le comportement. Donc, si vous faites des interfaces pour IDataFetcher, IDataUpdater, IDataInserter et IDataDeleter (par exemple), vous pouvez mélanger et assortir pour définir vos besoins via l'interface, puis avoir des implémentations qui prennent en charge tout ou partie d'entre eux, et je peux injectez toujours l'implémentation do-it-all à utiliser pendant que je construis l'application.
Paul
la source
GetById()
. Dois-je utiliserIRepository<T, TId>
,GetById(object id)
ou faire des hypothèses et utiliserGetById(int id)
? Comment fonctionneraient les clés composites? Je me suis demandé si une sélection générique par ID était une abstraction valable. Sinon, qu'est-ce que les référentiels génériques seraient obligés d'exprimer à la légère? C'était le raisonnement derrière l'abstraction de l' implémentation , pas de l' interface .Je préfère les référentiels spécifiques qui dérivent d'un référentiel générique (ou d'une liste de référentiels génériques pour spécifier le comportement exact) avec des signatures de méthode remplaçables.
la source
Avoir un référentiel générique enveloppé par un référentiel spécifique. De cette façon, vous pouvez contrôler l'interface publique tout en conservant l'avantage de la réutilisation du code qui provient d'un référentiel générique.
la source
classe publique UserRepository: Repository, IUserRepository
Ne devriez-vous pas injecter IUserRepository pour éviter d'exposer l'interface. Comme les gens l'ont dit, vous n'aurez peut-être pas besoin de la pile CRUD complète, etc.
la source