Question

I am building an Expression which should represent an Equals comparison of a property and a constant of type Nullable<long>. In other words, compiling the expression should return a lambda similar to x => (x.Id == value), where both Id and value are of type long?.

This is the code:

private static Expression<Func<T, bool>> GetNullableIdEqualsQuery(long? value)
{
    var type = typeof(T);

    var idProperty = type.GetProperty("Id");
    var xParam = Expression.Parameter(type, "x");

    var block = Expression.Block(
        typeof(bool),
        Expression.Equal(
            Expression.Property(xParam, idProperty),
            Expression.Constant(value, typeof(long?)))
        );

    return Expression.Lambda<Func<T, bool>>(block, xParam);
}

But when used in a query, it fails with an InvalidOperationException:

System.InvalidOperationException: variable 'x' of type 'SomeEntity' referenced from scope '', but it is not defined.

What am I doing wrong?

[Edit]

Thanks to @MarcGravell's answer, I've fixed the code. I am presuming that something was broken in the NHibernate's LINQ provider, but right now I don't have the time to investigate further.

If anyone needs a generic version which will (well, should) work for any property type, here it is:

public static Expression<Func<Tobj, bool>> GetEqualsQuery<Tobj, Tprop>(Tprop value, string propertyName)
{
    var type = typeof(Tobj);
    var property = type.GetProperty(propertyName);
    var propertyType = property.PropertyType;
    if (propertyType != typeof(Tprop))
        throw new InvalidOperationException("Property type ({0}) does not match the value type ({1})"
            .FormatWith(propertyType, typeof(Tprop)));

    var xParam = Expression.Parameter(type, "x");

    var body = Expression.Equal(
        Expression.Property(xParam, property),
        Expression.Constant(value, propertyType)
    );

    return Expression.Lambda<Func<Tobj, bool>>(body, xParam);
}

Test (for the lambda-compiled version only):

[TestClass]
public class ExpressionHelperTest
{
    class Test
    {
        public long Id { get; set; }
    }

    [TestMethod]
    public void GetEqualsQueryWorksForSimpleTypes()
    {
        // create a query for the lambda x => x.Id == 5
        var lambda = ExpressionHelper
            .GetEqualsQuery<Test, long>(5, "Id")
            .Compile();

        Assert.IsTrue(lambda(new Test() { Id = 5 }));
        Assert.IsFalse(lambda(new Test() { Id = 8 }));
    }
}
Était-ce utile?

La solution

Just don't use Expression.Block:

    var body = Expression.Equal(
            Expression.Property(xParam, idProperty),
            Expression.Constant(value, typeof(long?)));

    return Expression.Lambda<Func<T, bool>>(body, xParam);

Also note that some providers don't correctly implement the above when the value is null. If you need to look for null matches, you might need to special-case that scenario and explicitly check HasValue:

private static Expression<Func<T, bool>> GetNullableIdEqualsQuery<T>(long? value)
{
    var xParam = Expression.Parameter(typeof(T), "x");
    Expression body;
    if (value == null)
    {
        body = Expression.Not(
            Expression.Property(
                Expression.Property(xParam, "Id"),
                "HasValue"));
    }
    else
    {
        body = Expression.Equal(
            Expression.Property(xParam, "Id"),
            Expression.Constant(value, typeof(long?)));
    }
    return Expression.Lambda<Func<T, bool>>(body, xParam);
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top