Monday 27 October 2008

Searching, Ordering, and Paging with NHibernate

For a while now I've been looking at ways of making it as simple as possible to create scalable searches and add ordering and paging to them when using NHibernate as my ORM. I read this article by Oren a while back and it has guided my thinking since. I had the pleasure of mentioning this to Oren at the last ALT.NET drinks in London. He grabbed Seb's laptop and threw some code together, code very similar to this that has since appeared on his blog here.

I like the way that he has a class to organise the building of the criteria, it controls the complexity that filtering can quickly gain. I had a couple of reservations though. Primarily, the way that the filtering class assumes some of the responsibilities of the repository by executing the criteria. Relatedly, adding ordering and paging into the filtering class would seem to add additional responsibilities that do not belong to the class, but that I need to configure. So I've come up with a variant and with more than a little trepidation I'm putting it on my blog.

In the repository class I have a FindAll method that takes a DetachedCriteria object:

public IList<T> FindAll(DetachedCriteria criteria)
{
ICriteria toFire = criteria.GetExecutableCriteria(NHibernateHelper.OpenSession());
return toFire.List<T>();

}

To provide this DetachedCriteria I have a SearchCriteria object, in this case a CandidateSearchCriteria object. This is currently very simple in only providing for two fields but obviously more could be added to the interface and implemented. It provides a List of ICriterion objects.

public class CandidateSearchCriteria : List<ICriterion>, ICandidateSearchCriteria
{
private string _name;
public string Name
{
get { return _name; }
set
{
if (string.IsNullOrEmpty(value) || _name == value) return;
_name = value;
Add(Restrictions.Like("Name.FullName", value, MatchMode.Anywhere));
}
}

private DateRange? _registrationDate;
public DateRange? RegsitrationDate
{
get { return _registrationDate; }
set
{
if (_registrationDate == null || _registrationDate.Value.Equals(value.Value)) return;
_registrationDate = value;
AddRange(_registrationDate.Value.BuildCriterion());
}
}
}

public interface ICandidateSearchCriteria : IList<ICriterion>
{
string Name { get; set; }
DateRange? RegsitrationDate { get; set; }
}

To enable easy addition of paging and ordering concerns I've then created some extension methods:

public static DetachedCriteria Build<T> (this IList<ICriterion> list)
{
DetachedCriteria criteria = DetachedCriteria.For<T>();
foreach (ICriterion criterion in list)
{
criteria.Add(criterion);
}
return criteria;
}

public static DetachedCriteria Page (this DetachedCriteria criteria, int pageNumber, int pageSize)
{
criteria.SetMaxResults(pageSize);
criteria.SetFirstResult(pageSize*pageNumber - 1);
return criteria;
}

public static DetachedCriteria OrderBy (this DetachedCriteria criteria, string fieldName, Direction direction)
{
criteria.AddOrder(new Order(fieldName, direction.IsAscending()));
return criteria;
}

The first of these adds a Build extension to an IList<ICriterion>, this produces the DetachedCriteria object. The next two provide for a more 'Fluent' way to set the Paging and Ordering capabilities.

This all get used like this:

ICandidateRepository candidateRepository = MvcApplication.WindsorContainer.Resolve<ICandidateRepository>();
ICandidateSearchCriteria criteria = MvcApplication.WindsorContainer.Resolve<ICandidateSearchCriteria>();
if (!string.IsNullOrEmpty(name)) { criteria.Name = name; }

DetachedCriteria toFire = criteria.Build<Candidate>().OrderBy(sortBy.ToString(), direction).Page(fetchPage, recordsPerPage);

In particular this call is what I like:

criteria.Build<Candidate>().OrderBy(sortBy.ToString(), direction).Page(fetchPage, recordsPerPage);

Having exposed the DetachedCriteria from the searchCriteria class I can simply add the paging and ordering using the new extension methods.

My biggest dislike for this at the moment is that I have needed to reference the DetachedCriteria in my Repository's interface. This bleeding through of NHibernate into my interface is something that I try to minimise. Ideally I'd like to be able to create a repository layer using a different ORM, or even that persists to something other than a relational database, without having to change my interface too much.

Another thing that is unsettling me with this (unfinished) solution is the whole open-closed thing. It was pointed out to me the other day that this approach is not open to extension, and is not closed to modification. I had already tried to accommodate this to an extent by maintaining the SRP (allowing the paging & ordering to be outside of the filtering, and requiring collaboration with the Repository) through exposing the list of ICriterion, thinking that this would allow for some extension.

This is all very much a WIP for me at the moment and I think it'll be a while before I settle on anything. I suspect that I'll be blogging more on this topic before too long.

Thursday 2 October 2008

I Wish the Framework Team Used Interfaces More

One of the things that often bugs me about the .NET Framework is that so many of the classes are written without an interface. A case in point is the System.Net.Mail.SmtpClient class. This is an excellent class and very useful. It extends System.Object, but it does not implement an interface. This is extremely frustrating!

It's not that I would like to write my own version and swap it in, but I would like to be able to Mock it nicely and follow that development 101 practice of coding to the Interface. Not having an interface is very limiting, what if I want to use a Decorator pattern to implement logging (yes I know that it does some of this internally) or authorisation, no chance!

How hard would it have been to write it to an interface? It takes me no more than a couple of clicks and movements of the mouse.