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 }));
}
}
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);
}