The wonderful thing about abstract Repositories is that you can use the Decorator pattern to implement such cross-cutting concerns as caching.
As an example, given an IMyRepository interface, you can create a MyCachingRepository like this pseudo code:
public class MyCachingRepository : IMyRepository
{
private readonly IMyRepository repository;
public MyCachingRepository(IMyRepository repository)
{
if(repository == null)
{
throw new ArgumentNullException("repository");
}
this.repository = repository;
}
public Foo SelectFoo(int id)
{
Foo foo = ... // attempt to get foo from cache
if // foo is not it cache
{
foo = this.repository.SelectFoo(id);
// save foo in cache
}
return foo;
}
}
In this example, GetFoo is defined by IMyRepository. Notice how the decorated Repository is only invoked if the item isn't found by the cache.
This follows the Single Resposibility Principle because the real caching implementation can concentrate on retrieving and saving data, whereas the caching Decorator can concentrate on caching. This lets you vary the two independently of each other.
In our current project we used this approach, and what is extra nice is that we could do it as an afterthought without even touching the original Repositories. This is in spirit of the Open/Closed Principle.