Passing an expression to a method in NHibernate results in Object of type 'ConstantExpression' cannot be converted to type 'LambdaExpression'
Asked Answered
V

2

2

This problem occurs in both NHibernate 2 and 3. I have a Class A that has a member set of class B. Querying the classes directly executes nicely. But when I pass one of the expressions involving class B into a method I get the following error:

System.ArgumentException: Object of type 'System.Linq.Expressions.ConstantExpression' cannot be converted to type 'System.Linq.Expressions.LambdaExpression'.

As far as I can see I am passing the exact same expression into the Any() method. But for some reason they are treated differently. I have done some debugging and it looks like in the first method, the expression is treated as an expression with NodeType 'Quote', while the same expression in the 2nd method seems to be treated as an expression with NodeType 'Constant'. The parent expression of the expression in the 2nd method has a NodeType 'MemberAccess'. So it looks like the expression tree is different in the different test methods. I just don't understand why and what to do to fix this.

Classes involvend:

public class A
{
   public virtual int Id { get; set; }
   public virtual ISet<B> DataFields { get; set; }
}

public class B
{
   public virtual int Id { get; set; }
}

Sample test code:

    [TestMethod]
    public void TestMethod1()
    {
        using (ISession session = sessionFactory.OpenSession())
        {
            var records = session.Query<A>()
                                 .Where<A>(a => a.DataFields
                                                 .Any(b => b.Id == 1));
            Console.Write("Number of records is {0}", records.Count());
        }
    }

    [TestMethod]
    public void TestMethod2()
    {
        GetAsWhereB(b => b.Id == 1);
    }

    private void GetAsWhereB(Func<B, bool> where) 
    {
        using (ISession session = sessionFactory.OpenSession())
        {
            var records = session.Query<A>()
                                 .Where(a => a.DataFields
                                              .Any(where));
            Console.Write("Number of records is {0}", records.Count());
        }
    }
Villareal answered 19/1, 2011 at 16:56 Comment(0)
V
1

Not quite sure if this is the proper solution or not. The problem feels like a bug and my solution like a workaround. Nonetheless the following works for me, which boils down to creating a 'copy' of the given expression by using its body and parameter to construct a new expression.

private void GetAsWhereB(Func<B, bool> where) 
{
    Expression<Func<T, bool>> w = Expression.Lambda<Func<T, bool>>(where.Body, where.Parameters);
    using (ISession session = sessionFactory.OpenSession())
    {
        var records = session.Query<A>()
                             .Where(a => a.DataFields
                                          .Any(w));
        Console.Write("Number of records is {0}", records.Count());
    }
}
Villareal answered 17/4, 2011 at 23:42 Comment(1)
Where does the T generic parameter come from (in Expression<Func<T, bool>> w ...)? Did you intent to use B insteadAhumada
R
5

This is one problem:

private void GetAsWhereB(Func<B, bool> where) 

That's taking a delegate - you want an expression tree otherwise NHibernate can't get involved. Try this:

private void GetAsWhereB(Expression<Func<B, bool>> where)

As an aside, your query is hard to read because of your use of whitespace. I would suggest that instead of:

var records = session.Query<A>().Where<A>(a => a.DataFields.
                                 Any(b => b.Id == 1));

you make it clear that the "Any" call is on DataFields:

var records = session.Query<A>().Where<A>(a => a.DataFields
                                                .Any(b => b.Id == 1));

I'd also suggest that you change the parameter name from "where" to something like "whereExpression" or "predicate". Some sort of noun, anyway :)

Radicel answered 19/1, 2011 at 17:14 Comment(5)
Thank you Jon for you answer. Without going into discussing whitespacing and naming conventions, I can tell you that I have tried both <Func<B,bool> and Expression<Func<B,bool>>. With the latter I had to add an AsQueryable() after the a.DataFields, otherwise the overload accepting an Expression wasn't available. But alas, both cases resulted in the exact same error. So any other ideas perhaps?Villareal
@user581780: Ah, I hadn't spotted that DataFields was just a set. I suspect you'll need to change how NHibernate thinkgs of DataFields - is there something like EntitySet which implements IQueryable<T>? If you want NHibernate to translate the predicate into SQL, it has to be an expression tree.Radicel
Yes DataFields is an Iesi.Collections.Generic.ISet<T> collection. So you're saying that this collection should be a collection type that implements IQueryable<T>? First of all I don't know which class / interface I should be using. Any ideas? Second of all I still don't understand why the first method does work. It looks like I'm passing a <Func<B, bool>> argument to the Any() extension method like I did on the 2nd method.Villareal
@user581780: It would be something NHibernate-specific, probably. Note that there's a difference between the two cases - you're using AsEnumerable in one but not the other. Is that relevant?Radicel
Hi Jon, the AsEnumerable call is not relevant and I have removed it from my code sample (fixed the readability as well!), so there really is no difference as far as I can tell. Any ideas of why DataFields.AsQueryable() is not working for me? Would it be something NH specific or is this just conceptually wrong? Any word form a NH guru would be appreciated.Villareal
V
1

Not quite sure if this is the proper solution or not. The problem feels like a bug and my solution like a workaround. Nonetheless the following works for me, which boils down to creating a 'copy' of the given expression by using its body and parameter to construct a new expression.

private void GetAsWhereB(Func<B, bool> where) 
{
    Expression<Func<T, bool>> w = Expression.Lambda<Func<T, bool>>(where.Body, where.Parameters);
    using (ISession session = sessionFactory.OpenSession())
    {
        var records = session.Query<A>()
                             .Where(a => a.DataFields
                                          .Any(w));
        Console.Write("Number of records is {0}", records.Count());
    }
}
Villareal answered 17/4, 2011 at 23:42 Comment(1)
Where does the T generic parameter come from (in Expression<Func<T, bool>> w ...)? Did you intent to use B insteadAhumada

© 2022 - 2024 — McMap. All rights reserved.