Try fast search NHibernate

29 July 2010

NHibernate LINQ provider extension

In NHibernate almost all OO query systems (HQL, Criteria, QueryOver) are extensible. LINQ is, in its nature, a big extensible extension, but can you extend our LINQ-provider to translate your LINQ-extension in SQL ?
As usual in NHibernate, the answer is : yes, you can!!

The LINQ extension

As example I’m going to use an extension of string to mimic the SQL LIKE clause.
public static class MyLinqExtensions
{
    public static bool IsLike(this string source, string pattern)
    {
        pattern = Regex.Escape(pattern);
        pattern = pattern.Replace("%", ".*?").Replace("_", ".");
        pattern = pattern.Replace(@"\[", "[").Replace(@"\]","]").Replace(@"\^", "^");

        return Regex.IsMatch(source, pattern);
    }
}
I can use my extension in memory but what I need is instruct NHibernate about its translation for my persistence-queries.

The integration

The integration point is a class implementing : ILinqToHqlGeneratorsRegistry
The LinqToHqlGeneratorsRegistry provides the way to recognize and translate methods and properties of varios classes. Our DefaultLinqToHqlGeneratorsRegistry can manage LINQ extensions as Any, All, Min, Max etc., methods as StratsWith, EndsWith, Contains, IndexOf etc. or properties as Year, Month, Day, Length etc.
First of all I have to implements a IHqlGeneratorForMethod. NHibernate provides a cooked base class and my implementation will look as:

public class IsLikeGenerator : BaseHqlGeneratorForMethod
{
    public IsLikeGenerator()
    {
        SupportedMethods = new[] {ReflectionHelper.GetMethod(() => MyLinqExtensions.IsLike(null, null))};
    }

    public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject,
        ReadOnlyCollection<Expression> arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
    {
        return treeBuilder.Like(visitor.Visit(arguments[0]).AsExpression(),
                                visitor.Visit(arguments[1]).AsExpression());
    }
}

Having the HQL generator for my extension I can implements my ILinqToHqlGeneratorsRegistry extending the default implementation.

public class MyLinqToHqlGeneratorsRegistry: DefaultLinqToHqlGeneratorsRegistry
{
    public MyLinqToHqlGeneratorsRegistry():base()
    {
        RegisterGenerator(ReflectionHelper.GetMethod(() => MyLinqExtensions.IsLike(null, null)),
                          new IsLikeGenerator());
    }
}

The configuration

The last step to use my IsLike extension to query the DB is the configuration of my LinqToHQLGeneratorsRegistry.
The name of the property for the XML configuration is : linqtohql.generatorsregistry
In NHibernate 3 you can use the Loquacious-configuration :

configuration.LinqToHqlGeneratorsRegistry<MyLinqToHqlGeneratorsRegistry>();

 And now...

var contacts = (from c in db.Customers where c.ContactName.IsLike("%Thomas%") select c).ToList();

Welcome to the world of options,
Welcome to NHibernate3!!!

17 comments:

  1. That is probably one of the coolest things I've seen in the past months, regarding NH!

    ReplyDelete
  2. Very cool indeed. Does it still use ReLinq underneath?

    ReplyDelete
  3. re-linq is at the base of the expression transformation.

    ReplyDelete
  4. That's something that has been missing since Linq was released! Good work guys!

    ReplyDelete
  5. We have had some problems with NHibernate.Linq limitations before. This extensibility is very cool though, nice work

    ReplyDelete
  6. I cannot seem to resolve the DefaultLinqToHqlGeneratorsRegistry class ; where is it located (namespace ??) ??

    ReplyDelete
  7. I write a Blog about this:http://www.cnblogs.com/lyj/archive/2010/08/11/inside-nh3-linq-extension-query.html

    ReplyDelete
  8. Useful feature. We probably use it soon. Thanks.

    ReplyDelete
  9. Thank you for this great post !
    However, i have a problem when i try to apply this on "contains" keyword.
    Is something special for this ?

    Thank's by advance

    ReplyDelete
  10. Very nice. Thanks for this blog post and insight into this very cool extension point in NHibernate. LINQ can sometimes feel voodoo-like and this post reveals that it's not magic and shows us how we can introduce more advanced (or at least bespoke) LINQ-to-HQL translations. I'm itching to try this out myself.

    ReplyDelete
  11. I tried implementing this but keep getting a System.NotSupportedException. I posted a question on StackOverflow: http://stackoverflow.com/questions/4510511/linqtohql-extension-to-nhibernate-is-not-registering-properly-getting-system-not

    ReplyDelete
  12. Fabio,
    Does it make sense to have a similar behavior for HQL? I know we can always subclass xxxDialect, but this is less intrusive and DRY-friendly.

    ReplyDelete
  13. Fabio,
    excellent article, as always. But, i can try to do this using QueryOver, and stuck with it. There is no counter part of DefaultLinqToHqlGeneratorsRegistry for QueryOver?
    Is it possible anyway?

    Thanx, Vlado

    ReplyDelete
  14. Hi Fabio,

    Is it possible to access sub properties (that NH knows about), if you made the targetObject an Entity?

    for example, if you had the following query:
    query = query.Where (user => user.HasRole(_role));

    and the extension would actually look at the user.Role property?

    I realise that I could just do:
    query = query.Where (user => user.Role.IncludesRole(_role));

    and have a IncludesRole extension rather than a HasRole extension.
    But this is just an example to easily show what I am trying to get at.

    So in the BuildHql, there would be someway to get at the Role property and have the query still know that it was a database property.

    Regards
    Dewy

    ReplyDelete
  15. Please, give an example of this statement: "The name of the property for the XML configuration is : linqtohql.generatorsregistry"

    ReplyDelete