131 votes

Avantage de créer un référentiel générique par rapport à un référentiel spécifique pour chaque objet?

Nous sommes à élaborer un ASP.NET application MVC, et sont en train de construire le référentiel/classes de service. Je me demandais si il ya des grands avantages de la création d'un générique interface IRepository que tous les référentiels de mettre en œuvre, par rapport à chaque Référentiel d'avoir sa propre interface et un ensemble de méthodes.

Par exemple: un générique IRepository interface pourrait ressembler (prises à partir 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 de mise en œuvre de cette interface, par exemple:

  • CustomerRepository:IRepository
  • ProductRepository:IRepository
  • etc.

L'alternative que nous avons suivie lors de précédents projets 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) serait entièrement contenu dans le Référentiel de mise en œuvre, celui qui met en œuvre le service a juste besoin de savoir ce qui référentiel de la fonction à appeler.

Je suppose que je ne vois pas l'avantage d'écrire tous la syntaxe de l'expression dans la classe de service et de passer à l'référentiel. Ne serait-il pas dire facile à messup LINQ code est dupliqué dans de nombreux cas?

Par exemple, dans notre ancien système de facturation, nous appelons

InvoiceRepository.GetSingleInvoice(DateTime invoiceDate, int accountId)

à partir d'un petit nombre de services différents (Client, Facture, Compte, etc). Cela semble beaucoup plus propre que l'écriture de la suite dans plusieurs endroits:

rep.GetSingle(x => x.AccountId = someId && x.InvoiceDate = someDate.Date);

Le seul inconvénient que je vois à l'aide de l'approche spécifique que nous pourrions nous retrouver avec un grand nombre de permutations de Se* fonctions, mais cela semble encore préférable de pousser l'expression de la logique dans les classes de Service.

Ce qui me manque?

165voto

Bryan Watts Points 22810

C'est un problème aussi vieux que le modèle de Référentiel lui-même. L'introduction récente de LINQ IQueryable, un uniforme de la représentation d'une requête, a provoqué beaucoup de discussion sur ce thème.

Je préfère des référentiels spécifiques de moi-même, après avoir travaillé très dur pour construire un générique référentiel cadre. Peu importe ce que l'ingénieux mécanisme, j'ai essayé, j'ai toujours terminé dans le même problème: un référentiel est une partie du domaine modélisé, et ce domaine n'est pas générique. Pas chaque entité peut être supprimée, à chaque entité peut être ajouté, et pas à chaque entité dispose d'un référentiel. Les requêtes varier énormément; le référentiel de l'API devient aussi unique que l'entité elle-même.

Un modèle que j'utilise souvent est d'spécifiques référentiel d'interfaces, mais une classe de base pour les implémentations. Par exemple, l'utilisation de LINQ to SQL, vous pouvez faire:

public abstract class Repository<TEntity>
{
    private DataContext _dataContext;

    protected Repository(DataContext dataContext)
    {
        _dataContext = dataContext;
    }

    protected IQueryable<TEntity> Query
    {
        get { return _dataContext.GetTable<TEntity>(); }
    }

    protected void InsertOnCommit(TEntity entity)
    {
        _dataContext.GetTable<TEntity>().InsertOnCommit(entity);
    }

    protected void DeleteOnCommit(TEntity entity)
    {
        _dataContext.GetTable<TEntity>().DeleteOnCommit(entity);
    }
}

Remplacer DataContext avec votre unité de travail de choix. Un exemple de mise en œuvre pourrait être:

public interface IUserRepository
{
    User GetById(int id);

    IQueryable<User> GetLockedOutUsers();

    void Insert(User user);
}

public class UserRepository : Repository<User>, IUserRepository
{
    public UserRepository(DataContext dataContext) : base(dataContext)
    {}

    public User GetById(int id)
    {
        return Query.Where(user => user.Id == id).SingleOrDefault();
    }

    public IQueryable<User> GetLockedOutUsers()
    {
        return Query.Where(user => user.IsLockedOut);
    }

    public void Insert(User user)
    {
        InsertOnCommit(user);
    }
}

Avis de l'API publique de l'référentiel de ne pas autoriser les utilisateurs à supprimer. Aussi, exposant IQueryable est une toute autre boîte de pandore - il y a autant d'opinions que le ventre boutons sur le sujet.

26voto

Paul Points 8943

Je l'ai fait en désaccord légèrement avec Bryan. Je pense qu'il a raison, que, finalement, tout est très unique, et ainsi de suite. Mais dans le même temps, plus de qui vient de l'extérieur comme vous le design, et je trouve que l'obtention d'un générique référentiel et à l'utiliser tout en développant mon modèle, je peux obtenir une application très rapidement, puis refactoriser à une plus grande spécificité que je trouve le besoin de le faire.

Donc, dans de tels cas, j'ai souvent créé un générique IRepository a le CRUD de la pile, et qui me permet de revenir rapidement à jouer avec l'API et de laisser les gens jouer w/ l'INTERFACE utilisateur et de faire de l'intégration et de tests d'acceptation des utilisateurs en parallèle. Ensuite, comme je l'ai trouver j'ai besoin de requêtes spécifiques sur les pensions de titres, etc, j'ai commencer à remplacer que la dépendance w/ spécifique si nécessaire et va à partir de là. Un sous-jacente impl. il est facile de créer et d'utiliser (et, éventuellement, crochet à un en mémoire db ou les objets statiques ou moqué d'objets ou de quoi que ce soit).

Cela dit, ce que j'ai commencé à faire ces derniers temps, c'est de casser le comportement. Alors, si vous ne interfaces pour IDataFetcher, IDataUpdater, IDataInserter, et IDataDeleter (par exemple), vous pouvez mix-and-match de définir vos besoins par le biais de l'interface et ensuite avoir des implémentations de prendre soin de certains ou de la totalité d'entre eux, et je peux encore injecter le tout de la mise en œuvre de l'utiliser alors que je suis en train de construire l'application.

paul

13voto

Arnis L. Points 18316

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 un comportement exact) avec des signatures de méthode remplaçables.

6voto

Reddy Points 61

Greg Young a publié un excellent article sur ce sujet. http://codebetter.com/gregyoung/2009/01/16/ddd-the-generic-repository/

5voto

Peter Points 41

Avoir un référentiel générique encapsulé dans un référentiel spécifique. De cette façon, vous pouvez contrôler l’interface publique tout en ayant l’avantage de pouvoir réutiliser du code grâce à un référentiel générique.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X