Friday 9 April 2010

Using the latest NHibernate features in your VB.Net code.

No doubt you have tried the newest LINQ-to-NHibernate bits in your VB.Net application already. If you have gotten past retrieving all records from a specific table, you might been getting an exception similar to this one:

System.Exception: Unrecognised method call in epression CompareString(aUser.Name, value(Web.Controllers.Membership.AccountMembershipService+_Closure$__1).$VB$Local_userName, False)
at NHibernate.Impl.ExpressionProcessor.FindMemberExpression(Expression expression) in f:\Разные штуки\Visual Studio 2008\Projects\OpenSource\NHibernate\nhibernate\src\NHibernate\Impl\ExpressionProcessor.cs:line 170
at NHibernate.Impl.ExpressionProcessor.ProcessSimpleExpression(BinaryExpression be) in f:\Разные штуки\Visual Studio 2008\Projects\OpenSource\NHibernate\nhibernate\src\NHibernate\Impl\ExpressionProcessor.cs:line 295
at NHibernate.Impl.ExpressionProcessor.ProcessBinaryExpression(BinaryExpression expression) in f:\Разные штуки\Visual Studio 2008\Projects\OpenSource\NHibernate\nhibernate\src\NHibernate\Impl\ExpressionProcessor.cs:line 358
at NHibernate.Impl.ExpressionProcessor.ProcessExpression(Expression expression) in f:\Разные штуки\Visual Studio 2008\Projects\OpenSource\NHibernate\nhibernate\src\NHibernate\Impl\ExpressionProcessor.cs:line 388
at NHibernate.Impl.ExpressionProcessor.ProcessLambdaExpression(LambdaExpression expression) in f:\Разные штуки\Visual Studio 2008\Projects\OpenSource\NHibernate\nhibernate\src\NHibernate\Impl\ExpressionProcessor.cs:line 395
at NHibernate.Impl.ExpressionProcessor.ProcessExpression[T](Expression`1 expression) in f:\Разные штуки\Visual Studio 2008\Projects\OpenSource\NHibernate\nhibernate\src\NHibernate\Impl\ExpressionProcessor.cs:line 406
at NHibernate.Criterion.QueryOver`2.Add(Expression`1 expression) in f:\Разные штуки\Visual Studio 2008\Projects\OpenSource\NHibernate\nhibernate\src\NHibernate\Criterion\QueryOver.cs:line 568
at NHibernate.Criterion.QueryOver`2.Where(Expression`1 expression) in f:\Разные штуки\Visual Studio 2008\Projects\OpenSource\NHibernate\nhibernate\src\NHibernate\Criterion\QueryOver.cs:line 248
at NHibernate.Criterion.QueryOver`2.NHibernate.IQueryOver.Where(Expression`1 expression) in f:\Разные штуки\Visual Studio 2008\Projects\OpenSource\NHibernate\nhibernate\src\NHibernate\Criterion\QueryOver.cs:line 619

The code I have is quite innocent:
Return _session.QueryOver(Of User).Where(_
   Function(aUser) aUser.Name = userName).List().First

It returns the first instance of the User class that matches the Name property. The problem is that the VB compiler transforms the lambda into something like
(CompareString(aUser.Name, value(Web.Controllers.Membership.AccountMembershipService+_Closure$__1).$VB$Local_userName, False) = 0)

Now, CompareString is a shared method of the Microsoft.VisualBasic.CompilerServices.Operators class, which is probably not something the NHibernate developers expected to appear in a lambda. Time for a patch. A quick fix is to add the following piece of code in the beginning of the NHibernate.Impl.ExpressionProcessor.ProcessSimpleExpression method:

if (be.Left.NodeType == ExpressionType.Call) {
   var left = (MethodCallExpression) be.Left;
   if (left.Method.Name == "CompareString") {
      return ProcessSimpleExpression(Expression.Equal(left.Arguments[0], left.Arguments[1]));
   }
}

Actually, the CompareString method has 3 arguments. The third one, when set to false, signifies the literal comparison, while being set to true means textual (case insensitive) comparison. So, if you use the LIKE operator, you might want to check for left.Arguments[2].