Edit : My opinion on this subject have changed… You can
read the full story in
Back on Repositories and Paging. Introducing reporting.
The technique is still useful to write the query services, but I would not
recommend to implement it on a repository.
When it comes to repositories, people have a hard time figuring how to
respect the DDD vision while taking most out of current ORM technologies (Linq
and ORM) and not writing too much code – we’re so lazy.
The war between IRepository<T> generic repositories or not is raging
outside, and I took some time to chose my side. Here are the points to consider
:
- The repository is a contract between the domain and the infrastructure
- The implementation details should not leak outside
In my opinion, the first point indicates that the repository should
be tailored to the domain needs. It cannot be generic, or it is not a
contract at all.
When writing a contract, details matter !
This doesn’t mean that we cannot use generic tools to access data behind the
interface curtain. Linq DataContext and Tables<T> are very sharp tools to
implement repositories. And there is
a very good post by Greg Young about that.
There is still a point to be discussed though :
Should the repository methods return IEnumerable<T> or
IQueryable<T> ?
The IQueryable<T> is part of the framework, and cleanly integrated in
the language.
The problem is that its implementation depends heavily on the underlying
provider. And it is a really serious leak !
So lets state the question differently :
- Why would we need IQueryable ?
- Because we can add
new query clauses, and they will be executed directly in the database.
- What kind of clause would you add ?
- Don’t know…
clauses…
- Would it be business specifications ?
- No, these
should already be in the repository..
- So ?
- Sorting and Paging ! These are
presentation concerns !
- Here’s the point.
Paging is not a recent concern for programmers and there is never enough
tools to implement it properly. The main problem is that paging once you’ve got
all the data is less that effective. And this is what will happen with an
IEnumerable approach.
But let’s ask a two last questions.
Why is paging useful ? Is it really a presentation concern
?
We need paging to navigate through large collection of object, and if a
collection can grow enough so that is cannot be embraced in a single query, it
becomes a domain concern !
- When your object collection is known at design time to stay in small bounds
but you still want to page it for presentation clarity, there is no real
penalty to fetch all and display only a few.
- But when your collection can grow big, you SHOULD provide a mechanism to
retrieve only a range of it, for presentation purpose or simply for batching
purpose.
The problem is that if we leak IQueryable, the user can do far more than
paging, and problems can arise. So I suggest to use a new interface
IPaged<T> that would provide everything needed for
paging :
public interface IPaged<T> : IEnumerable<T>
{
///<summary>
/// Get the total entity
count.
///</summary>
int Count { get; }
///<summary>
/// Get a range of persited
entities.
///</summary>
IEnumerable<T> GetRange(int index, int
count);
}
And here is a simple implementation on a IQueryable :
public class Paged<T> :
IPaged<T>
{
private readonly
IQueryable<T> source;
public Paged(IQueryable<T> source)
{
this.source = source;
}
public IEnumerator<T> GetEnumerator()
{
return source.GetEnumerator();
}
IEnumerator
IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public int Count
{
get { return
source.Count(); }
}
public IEnumerable<T> GetRange(int index, int count)
{
return source.Skip(index).Take(count);
}
}
Then your repository can return IPaged collections like this without leaking
implementation details :
public
IPaged<Customer> GetCustomers();
This seems to be a major step in the repository pattern understanding, and
it’s underlying war. And you, on which side are you ?