Tuesday, October 18, 2011

Another NHibernate Repository

October 18, 2011 Posted by Jason Irwin No comments
I’ve been having a lot of fun with NHibernate recently and, during my spare time, have a few pet projects that I like to work on to improve my skillset (especially because it looks like my company is going to give an ORM a test run in the not too distant future). Everyone has their opinion on whether or not a Repository with an ORM is a good thing or a bad thing, and I’m not going to rehash any such arguments, except to say the following:
  • I needed to stop implementing the same old LoadSomethingById, UpdateSomething, SaveSomething, etc. methods for every single data type in my service layer. I found myself re-inventing the wheel, albeit with different naming conventions, over and over again
  • While NHibernate does provide a layer of abstraction in and of itself, I was reluctant to couple a particular ORM to my solution (i.e. call _session.Load<SomeType>(id) directly from my controller/codebehind/whatever)
So, as a learning exercise (like the world needed another one…) I created my own version of a Repository with an NHibernate solution as the default implementation. No doubt it’s not perfect but it was pretty worthwhile from an educational standpoint and I feel it is far cleaner than my previous approach. Feel Free to supply any constructive criticism you see fit.

My implementation is similar to a number already out there, aiming for a minimal set of non-specialized operations in my repository contract. Included is a pretty sweet simple FindMany (and FindManyFuture counterpart) method which takes an expression and uses the expression as my queryover’s filter. Combined with this is the ability to specify a limit on the number of items returned and the number of items you want to skip over. Both are useful if paging is necessary and I always like to specify a limit on all of my queries to stop any chance of returning an overly large number of records which will then hydrate objects and slow my application to a crawl.   Most of the other methods are pretty straightforward and correspond to common use cases when using any ORM.
My initial feeling is that this implementation as-is takes care of the bulk (~70%) ordinary  queries. For extraordinary queries, and those used repeatedly that I would like to reuse, I use extension methods rather than a service layer (as in the example below). This provides specific functions to specific repositories (based on the generic type) and allows me to use a single 

All in all, it looks a little something like this:

Here is the repository interface:
public interface IRepository<T, TId>
{
        T Get(TId id);
        IList<T> GetAll();
        T SaveOrUpdate(T entity);
        void Delete(T entity);
        ISession Session { get; }
        IList<T> FindMany(Expression<Func<T, bool>> expression = null, int skip = 0, int take = int.MaxValue);
        IEnumerable<T> FindManyFuture(Expression<Func<T, bool>> expression = null, int skip = 0, int take = int.MaxValue);
        T FindOne(Expression<Func<T, bool>> expression);
        T Load(TId id);
        T Save(T entity);
        T Update(T entity);
        void Delete(TId id);
}

Here is the default implementation:


  public class NHibernateRepository<T, TId> : IRepository<T, TId> where T : class
    {
        private readonly ISession _session;

        public NHibernateRepository(ISession session)
        {
            _session = session;
        }

        public virtual ISession Session
        {
            get { return _session; }
        }

        public IList<T> FindMany(Expression<Func<T, bool>> expression = null, int skip = 0, int take = int.MaxValue )
        {
            return expression != null ? Session.QueryOver<T>().Where(expression).Skip(skip).Take(take).List() : Session.QueryOver<T>().Skip(skip).Take(take).List();
        }

        public IEnumerable<T> FindManyFuture(Expression<Func<T, bool>> expression = null, int skip = 0, int take = int.MaxValue)
        {
            return expression != null ? Session.QueryOver<T>().Where(expression).Skip(skip).Take(take).Future<T>() : Session.QueryOver<T>().Skip(skip).Take(take).Future<T>();
        }

        public T FindOne(Expression<Func<T, bool>> expression)
        {
            return Session.QueryOver<T>().Where(expression).SingleOrDefault();
        }

        public virtual T Load(TId id)
        {
            return Session.Load<T>(id);
        }

        public virtual T Save(T entity)
        {
            Session.Save(entity);
            return entity;
        }

        public virtual T Update(T entity)
        {
            Session.Update(entity);
            return entity;
        }

        public virtual void Evict(T entity)
        {
            Session.Evict(entity);
        }

        public virtual void Delete(T entity)
        {
            Session.Delete(entity);
        }

        public virtual void Delete(TId id)
        {
            Session.Delete(Session.Load<T>(id));
        }


        public virtual T Get(TId id)
        {
            return Session.Get<T>(id);
        }

        public virtual IList<T> GetAll()
        {
            ICriteria criteria = Session.CreateCriteria(typeof (T));
            return criteria.List<T>();
        }

        public virtual T SaveOrUpdate(T entity)
        {
            Session.SaveOrUpdate(entity);
            return entity;
        }
   }

Example Extension Method


public static class ShopperExtensions
{
 public static Shopper GetShopperBySecurityToken(this IRepository<Shopper,int> repository, string securityToken)
 {
  return repository.Session.CreateCriteria(typeof (Shopper))
   .CreateAlias("ShopperSecurityProfile", "profile")
   .Add(Property.ForName("profile.SecurityToken").Eq(securityToken))
   .UniqueResult<Shopper>();
 }
}

Basic Usage



var shopperRepository = ObjectFactory.Container.GetInstance<IRepository<Shopper,int>>();

int shopperID = 123456;
var shopper = shopperRepository.Get(123456);

// OR USING SECURITY TOKEN AND EXTENSION METHOD

var shopper = shopperRepository.GetShopperBySecurityToken(securityToken);


In the above code you can see an example of using the Get method to get a particular shopper based on their ID and also a second example where an extension method is used to return a shopper based on their security token. (Note, in real-world code I would probably use constructor/property injection for my dependencies but go directly to the container in this example (ObjectFactory.Container…) for the sake of brevity.) The one change I definitely need to make is to remove the dependency on ISession from the repository interface. This (and now that I think of it maybe the Evict method) is the only place in the solution where the specific ORM solution leaks into my solution (so to speak). This property is only necessary so that any extension methods defined have a session they can use to hit the database.


0 comments: