Lambda Expressions with NHibernate.Linq

clock February 19, 2010 07:33 by author jamesstill

In the last two posts below I talked about creating a templated DataService class and also mused about whether UI data queries really were a cross-cutting concern. I'm agnostic about the question really; however, I do have one practical concern. I don't want to expose methods that take any old HQL query that the client sends my way. I'd like to have a controlled way of extending database queries down to the UI.

The answer is LINQ. Thanks to Rahien and others NHibernate.Linq 1.0 RTM is available. (Eventually this will be merged into the NHibernate mainline but for now it's a separate download.) Once you reference the assembly and put a reference to NHibernate.Linq the extension methods are available in your ISession instance:

   1:  // grab an ISession from ISessionFactory            
   2:  var query = from item in session.Linq()                 
   3:              select item;

So I'm going to extend my DataService class below to support LINQ queries along these lines. Kudos to Ryan Lanciaux who blogged on this before me. To support Linq queries we first must modify the IDataService interface to support lamdba expressions:
   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Linq.Expressions;
   5:  using System.Text;
   6:   
   7:  namespace ConcertMusic.DomainModel
   8:  {
   9:      public interface IDataService<T>
  10:      {
  11:          T Get(int id);
  12:          IList<T> Get(string hqlQueryString);
  13:          IList<T> Get();
  14:          void AddCriteria(Expression<Func<T, bool>> lambdaFunction);
  15:          T Save(T item);
  16:          bool Delete(T item);       
  17:      }
  18:  }

Now to implement the interface in the DataService class:

   1:  public class DataService<T> : IDataService<T>
   2:      {
   3:          private IList<Expression<Func<T, bool>>> _criteria; 
   4:   
   5:          public DataService() 
   6:          {
   7:             _criteria = new List<Expression<Func<T, bool>>>(); 
   8:          }
   9:   
  10:          public void AddCriteria(Expression<Func<T, bool>> lambdaFunction)
  11:          {
  12:              _criteria.Add(lambdaFunction);
  13:          } 
  14:   
  15:          public IList<T> Get()
  16:          {
  17:              using (ISession session = NHibernateHelper.OpenSession())
  18:              {
  19:                  var query = from item in session.Linq<T>()
  20:                              select item;
  21:   
  22:                  foreach (var criterion in _criteria)
  23:                  {
  24:                      query = query.Where<T>(criterion);
  25:                  }
  26:                  return query.ToList(); 
  27:              }         
  28:          }
  29:   
  30:          // other methods removed for clarity
  31:      }
  32:  }

That's pretty much it for the data access plumbing. Now the client code can fetch the IDataService instance and get back anything it wants using lambda expressions. Let's say I want to get every widget from the database with a name that starts with the letter "V". Here's my code:

   1:  IDataService<Widget> svc = DataServiceFactory<Widget>.Create();
   2:  svc.AddCriteria(item => item.Name.StartsWith("V"));
   3:  IList<Widget> list = svc.Get();

And these lambda expressions can be chained of course for very fine-grained search criteria. Suppose I want every widget with a name that starts with the letter "V", is round and blue, and has greater than 8 teeth:

   1:  IDataService<Widget> svc = DataServiceFactory<Widget>.Create();
   2:  svc.AddCriteria(item => item.Name.StartsWith("V"));
   3:  svc.AddCriteria(item => item.Color.Equals("Blue"));
   4:  svc.AddCriteria(item => item.Shape.Equals("Round"));
   5:  svc.AddCriteria(item => item.Teeth > 8));
   6:  IList<Widget> list = svc.Get();

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5


Refactor to DataService

clock February 1, 2010 15:23 by author jamesstill

In the post below on NHibernate 101 I said not to plumb data access code in the Operation Contract implementation. Now I'd like to refactor and get that out of there. Let's think about it a bit first. Do I really want to create a data layer class called "InstrumentService" with the CRUD in there? I could. But since this is a concert music application, I know I'm going to have several lookup tables in the system. In addition to musical instruments there will be musical genres, catalogs, recording labels, ensemble types, and so on and on.

So we want to make sure all of these data access classes implement an interface with the standard read, insert, update, and delete behavior. Let's do it. But let's be smart about it and take advantage of generics:

   1:  public interface IDataService<T>
   2:  {
   3:      IList<T> GetAll(string queryString);
   4:      T GetItem(int id);
   5:      T Save(T item);
   6:      bool Delete(int id, string tableName);
   7:      bool Delete(T item);
   8:  }

Having done that we can code a single DataService that implements the interface:

   1:  /// <summary>
   2:  /// Data service to fetch and save common lookup table items.
   3:  /// </summary>
   4:  public class DataService<T> : IDataService<T>
   5:  {
   6:      public DataService() { }
   7:   
   8:      public IList<T> GetAll(string queryString)
   9:      {
  10:          IList<T> list;
  11:          using (ISession session = NHibernateHelper.OpenSession())
  12:          {
  13:              IQuery query = session.CreateQuery(queryString);
  14:              list = query.List<T>();
  15:          }
  16:          return list;
  17:      }
  18:   
  19:   
  20:      public T GetItem(int id)
  21:      {
  22:          T item;
  23:          using (ISession session = NHibernateHelper.OpenSession())
  24:          {
  25:              item = session.Get<T>(id);
  26:          }
  27:          return item;
  28:      }
  29:   
  30:   
  31:      public T Save(T item)
  32:      {
  33:          using (ISession session = NHibernateHelper.OpenSession())
  34:          {
  35:              using (ITransaction transaction = session.BeginTransaction())
  36:              {
  37:                  session.SaveOrUpdate(item);
  38:                  transaction.Commit();
  39:              }
  40:          }
  41:          return item;
  42:      }
  43:   
  44:   
  45:      public bool Delete(int id, string tableName)
  46:      {
  47:          bool deleted = false;
  48:          using (ISession session = NHibernateHelper.OpenSession())
  49:          {
  50:              using (ITransaction transaction = session.BeginTransaction())
  51:              {
  52:                  string hqlString = string.Format("from {0} where ID = :ID", tableName);
  53:                  session.Delete(hqlString, id, NHibernateUtil.Int32);
  54:                  transaction.Commit();
  55:                  deleted = true;
  56:              }
  57:          }
  58:          return deleted;
  59:      }
  60:   
  61:      public bool Delete(T item)
  62:      {
  63:          bool deleted = false;
  64:          using (ISession session = NHibernateHelper.OpenSession())
  65:          {
  66:              using (ITransaction transaction = session.BeginTransaction())
  67:              {
  68:                  session.Delete(item);
  69:                  transaction.Commit();
  70:                  deleted = true;
  71:              }
  72:          }
  73:          return deleted;
  74:      }
  75:  }

Note that this implementation assumes that all of my lookup tables will have a surrogate primary key. There's just one last detail and that's creating a factory for these DataService instances. It's not really necessary. But I'm obsessed with web service methods that do as little as possible so I'm going to make their job as easy as possible. So here's the simple factory:

   1:  public class DataServiceFactory<T> where T : new()
   2:  {
   3:      public static DataService<T> Create()
   4:      {
   5:          return new DataService<T>();
   6:      }
   7:  }

That takes care of the NHiberate plumbing for all lookup tables in my system. Now if I want to fetch all musical instruments, or genres, or record labels from the system it's pretty easy:

   1:  public IList<Instrument> GetInstruments()
   2:  {
   3:      IDataService<Instrument> svc = DataServiceFactory<Instrument>.Create();
   4:      return svc.GetAll("FROM Instrument");
   5:  }
   6:   
   7:  public IList<Genre> GetGenres()
   8:  {
   9:      IDataService<Genre> svc = DataServiceFactory<Genre>.Create();
  10:      return svc.GetAll("FROM Genre");
  11:  }
  12:   
  13:  public IList<Catalog> GetCatalogs()
  14:  {
  15:      IDataService<Catalog> svc = DataServiceFactory<Catalog>.Create();
  16:      return svc.GetAll("FROM Catalog");
  17:  }

The one thing that still bugs me is the hard-coded "FROM [TableName]" that gets passed in. That could get refactored and put into the DataService base class. However, doing so would make an assumption that I'm not ready to make, namely, that my class name equals the table name. There is probably a way to decorate the POCO classes with attributes but then we're creeping back into domain entites that are not persistent ignorant. So it's a small price to pay.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5


About

SquareWidget LLC is an Oregon-based software development company that specializes in handcrafted .NET software solutions.

Search

Archive

Categories


Sign in