NHibernate 3 - Extending the Linq Provider to fix some System.NotSupportedException

Print Content | More

With the release of the new version NHibernate (3.0 alpha1), I’ve decided to give it a try and branch my current solution to switch to this new version and see hoe it goes.

I was especially interested in the new Linq support, cause I’ve decided to use it as the basis for my data access strategies.

After the necessary reference changes I run all my test suit...and I had bad news from the Linq provider in the form of some System.NotSupportedException like this one:

“UnitTest.Test03_Linq.QueryWithEqualsGuid:
System.NotSupportedException : Boolean Equals(System.Guid)”

Being NHibernate an open source project, instead of bothering the guys responsible for the project, my first approach is always to look at the code; so I downloaded the trunk and started looking around at the Linq provider. Watching how Equals() methods are handled by the parser it comes out fast that only the specific version to deal with strings is currently supported [bool Equals(string)], all other types have to rely on the ==operator.

But in my code I had a lot of filters based on Equals() call for various object types (int, guid and so on...) and I didn’t wanted to touch that code especially considering that with the previous Linq provider everything was working well.

However the solution is easy, just extend the default EqualsGenerator adding the support for the missing methods; but I didn’t wanted to compile a specific ‘patched’ version of NHibernate and this post from Fabio Maulo confirmed me you can easily extend the Linq provider. Great! That’s was exactly what I was looking for!

I started working on it and I had my second surprise Open-mouthed smile. The Linq provider was subhect of a heavy refactoring activity to provide better extensibility from the version you have in 3.0 alpha1. Using reflector and looking around in the binaries it comes out that to extend that provider you have to register your extension methods calling the methods of the NHibernate.Linq.Functions.FunctionRegistry class. But in all honesty I think that the way it works in alpha2 is way more elegant and it follows better the standard approach NHibernate have when it comes to configure its components.

So if you have to extend the Linq provider forget of alpha1 and compile your own version of NHibernate getting it from the Trunk.

Back to the job now: following Fabio’s instructions (and looking at the code) I came out with these classes:

public class ExtendedEqualsGenerator : BaseHqlGeneratorForMethod
{
	public ExtendedEqualsGenerator()
	{
		// the methods call are used only to get info about the signature, the actual parameter is just ignored
		SupportedMethods = new[] { 
			ReflectionHelper.GetMethodDefinition<Byte>(x => x.Equals((Byte)0)),
			ReflectionHelper.GetMethodDefinition<SByte>(x => x.Equals((SByte)0)),
			ReflectionHelper.GetMethodDefinition<Int16>(x => x.Equals((Int16)0)),
			ReflectionHelper.GetMethodDefinition<Int32>(x => x.Equals((Int32)0)),
			ReflectionHelper.GetMethodDefinition<Int64>(x => x.Equals((Int64)0)),
			ReflectionHelper.GetMethodDefinition<UInt16>(x => x.Equals((UInt16)0)),
			ReflectionHelper.GetMethodDefinition<UInt32>(x => x.Equals((UInt32)0)),
			ReflectionHelper.GetMethodDefinition<UInt64>(x => x.Equals((UInt64)0)),
			ReflectionHelper.GetMethodDefinition<Single>(x => x.Equals((Single)0)),
			ReflectionHelper.GetMethodDefinition<Double>(x => x.Equals((Double)0)),
			ReflectionHelper.GetMethodDefinition<Boolean>(x => x.Equals(true)),
			ReflectionHelper.GetMethodDefinition<Char>(x => x.Equals((Char)0)),
			ReflectionHelper.GetMethodDefinition<Decimal>(x => x.Equals((Decimal)0)),
			ReflectionHelper.GetMethodDefinition<Guid>(x => x.Equals(Guid.Empty)),
		};
	}

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

public class ExtendedLinqtoHqlGeneratorsRegistry : DefaultLinqToHqlGeneratorsRegistry
{
	public ExtendedLinqtoHqlGeneratorsRegistry()
	{
		this.Merge(new ExtendedEqualsGenerator());
	}
}

After registering them (actually it can be done only in code) using:

configuration.SetProperties("linqtohql.generatorsregistry", "Nhibernate.Extensions.ExtendedLinqtoHqlGeneratorsRegistry, Nhibernate.Extensions");

or

configuration.LinqToHqlGeneratorsRegistry<ExtendedLinqtoHqlGeneratorsRegistry>();

My tests passed again and I didn’t had to touch a single line of code. This is simply amazing! (but I think the full support for all the equals methods should have been added to the core anyway).



Nhibernate, Linq, Extensibility

4 comments

Related Post

  1. #1 da Adam Anderson - Sunday September 2010 alle 03:52

    Thanks for this. Ran in to this problem the other day, and this is just the solution I was looking for.

  2. #2 da Glenn - Thursday December 2010 alle 11:01

    Thanks for posting this. Helped me implementing something similar.

    But I have one question: why did you use "this.Merge(...)" in the ExtendedLinqtoHqlGeneratorsRegistry while Fabio uses "RegisterGenerator(...)". I like your version better but do you know if there are any differences?

  3. #3 da alessandro giorgetti - Monday December 2010 alle 10:07

    Hi, there aren't any difference. the RegisterGenerator() internally performs a merge too. I just think that using the merge call is actually cleaner and it's the same method that the actual Linq 'operators' uses to register themselves (if you look at the code). You can follow both approaches.

  4. #4 da Anthony Dewhirst - Friday September 2011 alle 03:03

    Hi, I am following yours and fabio's example and running into a bit of an issue.
    We have our own Custom type Date that wraps a DateTime. In the mappings for properties that use this type we have a IUser type defined that maps to NHibernate.SqlTypes.SqlTypeFactory.Date.

    I have created a custom dialect and registered the following classes and methods:
    public class AddDaysGenerator : BaseHqlGeneratorForMethod
    {
    public AddDaysGenerator()
    {
    SupportedMethods = new[] {ReflectionHelper.GetMethodDefinition<Date>(d => d.AddDays(0))};
    }

    #region Overrides of BaseHqlGeneratorForMethod

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

    #endregion
    }

    public class AddDaysGeneratorRegistry : DefaultLinqToHqlGeneratorsRegistry
    {
    public AddDaysGeneratorRegistry()
    {
    RegisterGenerator(ReflectionHelper.GetMethodDefinition<Date>(d => d.AddDays(0)), new AddDaysGenerator());
    }
    }

    public class CustomDialect : MsSql2008Dialect
    {
    public CustomDialect()
    {
    RegisterFunction("AddDays", new SQLFunctionTemplate(NHibernateUtil.DateTime, "DATEADD(DD,?1,?2)"));
    }
    }

    as you can see, this picks up our method AddDays defined on our custom Date class. This means that we can do something like:

    query = query.Where(dc => (dc.EndDateReminderPeriod != null && (((dc.EndDate).AddDays(0-dc.EndDateReminderPeriod.Value)) == _reminderDate)) ||
    (dc.RealEndDateReminderPeriod != null && (((dc.RealEndDate).AddDays(0-dc.RealEndDateReminderPeriod.Value)) == _reminderDate)));

    Where in this example XXReminderPeriod is an int and XXDate is a Date property.

    I "think" that this fails because the return type of the function is DateTime and so when it gets to the equality part of the method, its trying to put the _reminderDate into a DateTime. But as I am sure you have guessed, _reminderDate is a Date.
    I was trying to change the function to use my custom transaltor for the return type, but it seems that SQLFunction only accepts a IType and not an IUserType.

    Any thoughts?

All fields are required and you must provide valid data in order to be able to comment on this post.


(will not be published)
(es: http://www.mysite.com)


  1. #1 da http://www.primordialcode.com/blog/post/nhibernate-customize-linq-provider-user-defined-sql-functions

    www.primordialcode.com - NHibernate - Customize the Linq provider to call your user defined SQL functions

  2. #2 da http://wordpress.primordialcode.com/index.php/2010/10/01/nhibernate-customize-linq-provider-user-defined-sql-functions/

    NHibernate &#8211; Customize the Linq provider to call your user defined SQL functions at Primordial Code

  3. #3 da http://www.primordialcode.com/blog/post/linq-to-nhibernate-string.equals-with-stringcomparison-option

    www.primordialcode.com - Linq to NHibernate - String.Equals with StringComparison option

  4. #5 da http://devio.wordpress.com/2012/08/20/nhibernate-string-comparison/

    NHibernate String Comparison &laquo; devioblog