I just blogged that I'm interested in using IQueryable for my repositories, so I can do this kind of thing:
var orders = repos.FindOrdersByStatus(OrderStatus.Cancelled)
.Take(10)
.Skip(10)
.ToList();
LINQ for NHibernate allows this quite easily - I can return an IQueryable by doing something like this:
public IQueryable<Order> FindOrdersByStatus(OrderStatus status){
return _session.Linq<Order>.Where(o.Status == status);
}
But what if you want to use HQL?
Linq for NHibernate isn't quite ready yet, so I usually find myself choosing NHibernate's HQL for many queries. Therefore, I still need a way of getting something like IQueryable from HQL.
There is a solution I quite like, and it's actually very simple.
I just invent a new interface that looks like IQueryable, but only exposes a subset of the methods I need.
// Uses method chaining for fluency.
// IAlmostQueryable is a bad name. Soz.
public interface IAlmostQueryable<T>
{
IAlmostQueryable<T> Skip(int startAt);
IAlmostQueryable<T> Take(int take);
IEnumerable<T> AsEnumerable();
IList<T> ToList();
}
I can now have my repositories use this interface for searches, and clients won't have to know about NHibernate.
public IAlmostQueryable<Order> FindOrdersByStatus(OrderStatus status)
{
var nhQuery =
_nhibernateSession
.CreateQuery("from o in Order where o.OrderStatus = :status");
nhQuery.SetParameter("status", status);
return new HqlAlmostQueryable( nhQuery );
}
And calling code can now do this:
var orders = repos.FindOrdersByStatus(OrderStatus.Cancelled)
.Take(10)
.Skip(10)
.ToList();
...with the following benefits
- HQL can be used behind the scenes
- No dependency on NHibernate exposed to domain/test/clients.
- Pagination takes place on the server, not in memory.
- It will easily port to Linq for NHibernate if/when the time comes
- You can easily write adapters for any data access library/API (LINQ2SQL, ICriteria etc)
- (maybe) I can enhance this interface to include more functionality.
- (maybe) Data Access specific features are exposed explicitly in the IAlmostQueryable (whereas in IQueryable you might not instantly know what provider is working behind the scenes).
You might be wondering what that HqlAlmostQueryable class is all about. It's basically a very trivial implementation of IAlmostQueryable.
public class HqlAlmostQueryable<T> : IAlmostQueryable<T>
{
private int _skip;
private int _take;
private readonly IQuery _query;
public HqlAlmostQueryable(NHibernate.IQuery q)
{
_query = q;
}
public IAlmostQueryable<T> Skip(int records)
{
_skip = records;
return this;
}
public IAlmostQueryable<T> Take(int records)
{
_take= records;
return this;
}
public IEnumerable<T> AsEnumerable()
{
_query.SetMaxResults(_take);
_query.SetFirstResult(_skip);
return _query.Enumerable<T>();
}
public IList<T> ToList()
{
_query.SetMaxResults(_take);
_query.SetFirstResult(_skip);
return _query.List<T>();
}
}
These can easily be written for ICriteria or any other API.
Alternative: Just use NHibernate IQuery?
I considered this alternative. If I avoid IQueryable altogether, I could just return a NHibernate IQuery instead of a Linq IQueryable. This would let me do this:
var orders = repos.FindOrdersByStatus(OrderStatus.Cancelled)
.SetMaxResults(1)
.SetFirstResult(10)
.List<Order>();
Unfortunately this doesn't tick enough boxes. My domain layer would be dependent on NHibernate, and therefore anything that is dependent on the domain will also be dependent on NHibernate (including tests). As we know, the nice thing about the repository pattern is it's ability to encapsulate data access and free surrounding code from dependencies on specific data access libraries.