Extending LINQ to Nhibernate provider, in combination with Dynamic LINQ problem
Asked Answered
R

3

6

I'm using NHibernate 3.1.0 and I'm trying to extend the LINQ provider by using BaseHqlGeneratorForMethod and extending the DefaultLinqToHqlGeneratorsRegistry as explained in Fabio's post.

For example, to support ToString() I've created a ToStringGenerator as below.

internal class ToStringGenerator : BaseHqlGeneratorForMethod
{
    public ToStringGenerator()
    {
        SupportedMethods = new[]
            {
                ReflectionHelper.GetMethodDefinition<object>(x => x.ToString())
            };
    }

    public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection<Expression> arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
    {
        return treeBuilder.Cast(visitor.Visit(targetObject).AsExpression(), typeof(string));
    }
}

and I have registered using

internal class CustomLinqToHqlGeneratorsRegistry : DefaultLinqToHqlGeneratorsRegistry
{
    public CustomLinqToHqlGeneratorsRegistry()
    {
        this.Merge(new ToStringGenerator());
    }
}

etc. So far this works for "static" queries, I can use it like this:

var results = mSession.Query<Project>();
string pId = "1";
results = results.Where(p => p.Id.ToString().Contains(pId));

This translates correctly to its SQL counterpart (using SQL Server 2008)

where cast(project0_.Id as NVARCHAR(255)) like (''%''+@p0+''%'')

The problem arises when I try to use it in combination with Microsoft Dynamic LINQ library (discussed in this Scott Guthrie's post) like this:

var results = mSession.Query<Project>();
string pId = "1";
results = results.Where("Id.ToString().Contains(@0)", pId);

This results in a NotSupportedException with a message of "System.String ToString()" (which was the exact same messages I was getting with the static queries before implementing the classes mentioned above). This exception is being thrown with a source of "NHibernate" and with the StackTrace at "at NHibernate.Linq.Visitors.HqlGeneratorExpressionTreeVisitor.VisitMethodCallExpression(MethodCallExpression expression)".

So what am I missing here? What have I done wrong, or what needs to be done to support this scenario?

Roselinerosella answered 7/6, 2011 at 15:46 Comment(0)
T
3

I had the same problem and fixed it.
At first I want to thank murki for providing the information which got me on my way!

The answer lies partly in Fabio's post. To solve this issue you have to use the RegisterGenerator instead of the Merge method in the CustomLinqToHqlGeneratorsRegistry constructor. My implementation of the CustomLinqToHqlGeneratorsRegistry class is as follows:

public class CustomLinqToHqlGeneratorsRegistry : DefaultLinqToHqlGeneratorsRegistry
{
    public CustomLinqToHqlGeneratorsRegistry()
        : base()
    {
        MethodInfo toStringMethod = ReflectionHelper.GetMethodDefinition<int>(x => x.ToString());
        RegisterGenerator(toStringMethod, new ToStringGenerator());
    }
}
Twofold answered 4/1, 2012 at 8:38 Comment(1)
That sounds like a bug in the Merge implementation then.Roselinerosella
K
1

There are two, well defined, separate stages here:

  1. Converting the dynamic (string) query into a static expression (done by the Dynamic Linq library)
  2. Parsing that into an HqlTree, then executing (done by NHibernate)

Since you have determined that a static expression works well, the problem lies in 1.

What happens if you do the following?

var results = Enumerable.Empty<Project>().AsQueryable();
string pId = "1";
results = results.Where("Id.ToString().Contains(@0)", pId);

If it fails, you'll have confirmed it's a problem with Dynamic Linq alone (i.e. it doesn't support the expression you're feeding it), so you'll have to dig into it and patch it.

Semi-related: the ToStringGenerator looks useful; could you submit a patch for NHibernate? http://jira.nhforge.org

Kelleykelli answered 7/6, 2011 at 16:22 Comment(2)
Thanks for your answer. I have done what you suggested and the query works fine (I mean it yields no results but the Expression inside the query is correctly generated). Something that I have noticed now comparing the 2 queries is that the Expression for the static one uses a Expression.Constant with the variable (pId) value whereas the dynamic one uses the string value directly. Do you think this may have to do with the problem?Roselinerosella
@murki: yes, it might be related. High-impedance expression trees are extremely fragile in their support for small variations. Now, given this you have two paths: patching Dynamic Linq to emit an Expression.Constant, or patching NHibernate to support the usage emitted by Dynamic Linq.Kelleykelli
C
0

Supposing the property Id of the class Project it's an Int32 try registering the corresponding Int32.ToString() method in your ToStringGenerator class.

...
public ToStringGenerator()
{
    SupportedMethods = new[]
        {
            ReflectionHelper.GetMethodDefinition<object>(x => x.ToString()),
            ReflectionHelper.GetMethodDefinition<int>(x => x.ToString()),
        };
}
...
Chromatograph answered 8/6, 2011 at 15:48 Comment(2)
Yeah, it's an Int32, but I'd actually tried that before -and this is probably topic for another question, but: 1) if I put only object and int nothing changes in my error, 2) if i start putting more data types (e.g. DateTime), it throws an NhibernateException with the error "An item with the same key has already been added" when building the SessionFactory.Roselinerosella
Ok thanks. After some investigation, NHibernate 3.2, still in beta version, will have a built in ToString support in Linq.Chromatograph

© 2022 - 2024 — McMap. All rights reserved.