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.