Tuesday, November 1, 2011

Strongly Typed HQL


If you're using NHibernate, you have 3 main options to execute a query against the DB: Criteria API, HQL and SQL. The last one won't be relevant to our discussion.

For a long time, the names of the properties were a weakness point since they were represented by strings. For example:

var crit = Session.CreateCriteria(typeof(Person));
crit.Add(NHibernate.Criterion.Expression.Eq("ID", someVal));

Lately, Criteria API has an extension to support Lambda expressions and therefore can be strongly typed:

var crit = Session.CreateCriteria(typeof(Person));
crit.Add<Person>(p => p.ID == someVal)

But in some complicated cases - HQL is inevitable, and since HQL is represented by string - refactoring can be very painful. For example:

string hql = "select p from Person p where p.ID=:someVal"

To avoid this problem, I've created 2 methods. To use these methods, simply locate them in the base class of all the classes that build HQL quries. In our case it was BaseRepository which is the base class for all the Repositories (if you're following DDD). These 2 methods are:

Class<T>();
Prop<T>(Expression<Func<T, object>> property);

I will show the implementation soon, but first an example:

string hql = "select p from " 
+ Class<Person>() + " p where p." + Prop<Person>(x => x.ID) + "=:someVal"

And the implementation:

public string Class<T>()
{
    return typeof(T).ToString();
}

public string Prop<T>(Expression<Func<T, object>> property)
{
    if (property.Body.NodeType == ExpressionType.MemberAccess)
    {
        return (property.Body as MemberExpression).Member.Name;
    }
    else if (property.Body.NodeType == ExpressionType.Convert
        && (property.Body as UnaryExpression).Operand.NodeType == ExpressionType.MemberAccess)
{
        return ((property.Body as UnaryExpression).Operand as MemberExpression).Member.Name;
}

    return "";
}

4 comments:

  1. I was looking for a strongly typed solution like this.. :) Thanks !
    ^^

    ReplyDelete
  2. Hi Itzik,
    You mentioned that sometimes HQL is inevedable. I think that you ment cases when we want to query inner properties like Person.Address.Street == "myStreet".
    The way you implemented the runtime property name accessor you will always get the innerest property name.
    Prop(x => x.Address.Street) will get you "street" and not Address.Street as we wanted.

    Im still looking for the solution :)

    ReplyDelete
    Replies
    1. Hi Dennis,

      You right, the code i've published doesn't support inner properties, i usually create joins for that propose.

      But here is my implementation for inner properties:

      http://www.sendspace.com/file/ufo9m0

      Anyway, feel free to use it/change it however you like :)

      Delete