139

We are developing an ASP.NET MVC application, and are now building the repository/service classes. I'm wondering if there are any major advantages to creating a generic IRepository interface that all repositories implement, vs. each Repository having its own unique interface and set of methods.

For example: a generic IRepository interface might look like (taken from this answer):

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();
}

Each Repository would implement this interface, for example:

  • CustomerRepository:IRepository
  • ProductRepository:IRepository
  • etc.

The alternate that we've followed in prior projects would be:

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);
}

In the second case, the expressions (LINQ or otherwise) would be entirely contained in the Repository implementation, whoever is implementing the service just needs to know which repository function to call.

I guess I don't see the advantage of writing all the expression syntax in the service class and passing to the repository. Wouldn't this mean easy-to-messup LINQ code is being duplicated in many cases?

For example, in our old invoicing system, we call

InvoiceRepository.GetSingleInvoice(DateTime invoiceDate, int accountId)

from a few different services (Customer, Invoice, Account, etc). That seems much cleaner than writing the following in multiple places:

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

The only disadvantage I see to using the specific approach is that we could end up with many permutations of Get* functions, but this still seems preferable to pushing the expression logic up into the Service classes.

What am I missing?

Community
  • 1
  • 1
Beep beep
  • 18,873
  • 12
  • 63
  • 78
  • Using generic repositories with full ORMs looks useless. I have discussed this in details [here](https://stackoverflow.com/a/51781877/5779732). – Amit Joshi Sep 11 '19 at 07:47

5 Answers5

177

This is an issue as old as the Repository pattern itself. The recent introduction of LINQ's IQueryable, a uniform representation of a query, has caused a lot of discussion about this very topic.

I prefer specific repositories myself, after having worked very hard to build a generic repository framework. No matter what clever mechanism I tried, I always ended up at the same problem: a repository is a part of the domain being modeled, and that domain is not generic. Not every entity can be deleted, not every entity can be added, not every entity has a repository. Queries vary wildly; the repository API becomes as unique as the entity itself.

A pattern I often use is to have specific repository interfaces, but a base class for the implementations. For example, using LINQ to SQL, you could do:

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);
    }
}

Replace DataContext with your unit-of-work of choice. An example implementation might be:

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);
    }
}

Notice the public API of the repository does not allow users to be deleted. Also, exposing IQueryable is a whole other can of worms - there are as many opinions as belly buttons on that topic.

Brody Robertson
  • 8,506
  • 2
  • 47
  • 42
Bryan Watts
  • 44,911
  • 16
  • 83
  • 88
  • 5
    So how would you use IoC/DI with this? (I'm a novice at IoC) My question in regards to your pattern in full: http://stackoverflow.com/questions/4312388/how-should-i-use-ioc-di-with-this-repository-pattern – dan Nov 30 '10 at 10:55
  • 40
    " a repository is a part of the domain being modeled, and that domain is not generic. Not every entity can be deleted, not every entity can be added, not every entity has a repository" perfect! – adamwtiko Feb 08 '12 at 08:26
  • I know this is an old answer, but I'm curious if leaving out an Update method from the Repository class with intentional. I'm having trouble finding a clean way to do this. – rtf Apr 09 '15 at 22:43
  • @Tanner: Updates are implicit - when you modify a tracked object, then commit the `DataContext`, LINQ to SQL issues the appropriate command. – Bryan Watts Apr 09 '15 at 23:14
  • 1
    Oldie but a goodie. This post is incredibly wise and should be read and re-read but all well-to-do developers. Thanks @BryanWatts. My implementation is typically dapper based, but premise is the same. Base repository, with specific repositories to represent the domain that opt-in to features. – pim May 02 '18 at 20:29
28

I actually disagree slightly with Bryan's post. I think he's right, that ultimately everything is very unique and so on. But at the same time, most of that comes out as you design, and I find that getting a generic repository up and using it while developing my model, I can get an app up very quickly, then refactor to greater specificity as I find the need to do so.

So, in cases like that, I have often created a generic IRepository that has the full CRUD stack, and that lets me get quickly to playing with the API and letting folks play w/ the UI and do integration & user acceptance testing in parallel. Then, as I find I need specific queries on the repo, etc, I start replacing that dependency w/ the specific one if needed and going from there. One underlying impl. is easy to create and use (and possibly hook to an in-memory db or static objects or mocked objects or whatever).

That said, what I have started doing lately is breaking up the behavior. So, if you do interfaces for IDataFetcher, IDataUpdater, IDataInserter, and IDataDeleter (for example) you can mix-and-match to define your requirements through the interface and then have implementations that take care of some or all of them, and I can still inject the does-it-all implementation to use while I'm building the app out.

paul

Paul
  • 35,689
  • 11
  • 93
  • 122
  • 4
    Thanks for the reply @Paul. I actually tried that approach as well. I couldn't figure out how to generically express the very first method I tried, `GetById()`. Should I use `IRepository`, `GetById(object id)`, or make assumptions and use `GetById(int id)`? How would composite keys work? I wondered if a generic selection by ID was a worthwhile abstraction. If not, what else would generic repositories be forced to express akwardly? That was the line of reasoning behind abstracting the *implementation*, not the *interface*. – Bryan Watts Apr 06 '10 at 22:54
  • 11
    Also, a generic query mechanism is the responsibility of an ORM. Your repositories should be implementing the specific queries for your project's entities by *using* the generic query mechanism. The consumers of your repository should not be forced to write their own queries unless that is part of your problem domain, such as with reporting. – Bryan Watts Apr 06 '10 at 23:00
  • 2
    @Bryan - Regarding your GetById(). I use a FindById(TId id); Thus resulting in something like repository.FindById(435); – Joshua Hayes Oct 21 '10 at 00:47
  • I don't usually put field-level query methods on the generic interfaces, frankly. As you pointed out, not all models should be queried by a single key, and in some cases your app won't ever retrieve something by an ID at all (e.g. if you're using DB-generated primary keys and you only fetch by a natural key, for example login name). The query methods evolve on the specific interfaces that I build as part of refactoring. – Paul Oct 21 '10 at 14:07
13

I prefer specific repositories which derives from generic repository (or list of generic repositories to specify exact behavior) with overridable method signatures.

Arnis Lapsa
  • 45,880
  • 29
  • 115
  • 195
  • Could you please provide a small snippet-ish example? – Johann Gerell Mar 25 '11 at 10:13
  • @Johann Gerell no, because I don't use repositories anymore. – Arnis Lapsa Mar 25 '11 at 10:35
  • what do you use now that you stay away from repositories? – Chris Mar 31 '11 at 21:01
  • @Chris i focus heavily on having rich domain model. input part of application is what's important. if all state changes are carefully monitored, it doesn't matter that much how You read data as long as it's efficient enough. so I just use NHibernate ISession directly. without repository layer abstraction, it's way much easier to specify stuff like eager loading, multi queries, etc. and if You really need, it's not hard to mock out ISession too. – Arnis Lapsa Mar 31 '11 at 23:10
  • @Arnis - this may seem easier/faster/simpler at first but it will become a problem quickly the first time you have to re-use any of that query logic in more than one place of your domain model. Having the repository layer separate from your business domain but used by it makes it easy to re-use it for other things. Although I will admit, for smaller systems, this simplification might be well-worth the risk because chances are there won't be much or any re-use in the first place. – Jesse Webb Sep 16 '11 at 16:45
  • 4
    @JesseWebb Naah... With rich domain model query logic gets simplified significantly. E.g. if I want to search for users that have purchased something, I just search for users.Where(u=>u.HasPurchasedAnything) instead of users.Join(x=>Orders, something something, I dont know linq).Where(order=>order.Status==1).Join(x=>x.products).Where(x.... etc etc etc.... blah blah blah – Arnis Lapsa Oct 07 '11 at 17:43
5

Have a generic repository that is wrapped by a specific repository. That way you can control the public interface but still have the advantage of code-reuse that comes from have a generic repository.

3

public class UserRepository : Repository, IUserRepository

Shouldn't you inject IUserRepository to avoid exposing the interface. As people have said, you may not need the full CRUD stack etc.

Ste
  • 31
  • 1