Reputation: 31
I am trying to create a generic Expression builder, which basically works fine as long as none the objects values is null.
My current code looks like this (StartsWith as an example):
case FilterOperationTypes.StartsWith:
{
ParameterExpression e = Expression.Parameter(typeof(T), "e");
PropertyInfo propertyInfo = typeof(T).GetProperty(field);
MemberExpression m = Expression.MakeMemberAccess(e, propertyInfo);
ConstantExpression c = Expression.Constant(val, typeof(string));
MethodInfo mi = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
Expression call = Expression.Call(m, mi, c);
return Expression.Lambda<Func<T, bool>>(call, e);
}
Let's assume field is the Property CustomerName. I understand that the actual final expression will be like:
e.CustomerName.StartsWith(val)
and if CustomerName is null it will, of course, fail to call the StartsWith Method, which is perfectly clear.
I have tried to do something like this:
case FilterOperationTypes.StartsWith:
{
ParameterExpression e = Expression.Parameter(typeof(T), "e");
PropertyInfo propertyInfo = typeof(T).GetProperty(field);
MemberExpression m = Expression.MakeMemberAccess(e, propertyInfo);
ConstantExpression c = Expression.Constant(val, typeof(string));
MethodInfo mi = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
Expression call = Expression.IfThenElse(
Expression.Equal(m, Expression.Constant(null)),
Expression.Constant(null),
Expression.Call(m, mi, c));
//Expression.Call(m, mi, c);
return Expression.Lambda<Func<T, bool>>(call, e);
}
But this produces a Expression of type 'System.Void' cannot be used for return type 'System.Boolean' Exception.
I am a little bit lost as of now. Maybe you guys can push me in the right direction.
Thanks a lot!
Upvotes: 0
Views: 924
Reputation: 31
Got it working now. Big thanks to Servy for pushing me in the right direction.
The Expression.Condition was exactly what I needed to achieve my goal. Since the actual object and the object's property can be null at run time I had to nest two Conditions:
PropertyInfo propertyInfo = typeof(T).GetProperty(fieldName);
MemberExpression m = Expression.MakeMemberAccess(e, propertyInfo);
ConstantExpression c = Expression.Constant(comparisonValue, typeof(string));
MethodInfo mi = typeof(string).GetMethod(methodName, new Type[] { typeof(string) });
Expression call = Expression.Condition(Expression.Equal(e, Expression.Constant(null)),
Expression.NotEqual(e, Expression.Constant(null)),
Expression.Condition(Expression.Equal(Expression.Property(e, fieldName), Expression.Constant(null)),
Expression.NotEqual(e, Expression.Constant(null)), Expression.Call(m, mi, c)));
return Expression.Lambda<Func<T, bool>>(call, e);
In case of a null value I decided to use
Expression.NotEqual(e, Expression.Constant(null))
to be sure to receive "false". I'm sure there is a nicer way to do this, but I could not think of one, yet.
The final Expression will look like this
{e => IIF((e == null), (e != null), IIF((e.CustomerName== null), (e != null), e.CustomerName.StartsWith("547")))}
As of now I am pretty happy with the solution, though I'll try to optimize it.
My current implementation and usage of the code looks like this. Maybe it helps one of you guys:
Expression<Func<T, bool>> GetDetailEx<T>(string fieldName, object comparisonValue, string filterType)
{
var e = Expression.Parameter(typeof(T),"e");
switch (GetFilterOperationType(filterType))
{
case FilterOperationTypes.Between:
//Between is automatically translated in >= AND <= within the ExecuteDeepFilter-Method
break;
case FilterOperationTypes.GreaterThanOrEquals:
{
return GenerateConditionalExpression<T>(fieldName, comparisonValue, Expression.GreaterThanOrEqual(
Expression.Convert(Expression.Property(e, fieldName), Expression.Constant(comparisonValue).Type),
Expression.Constant(comparisonValue)),e);
}
case FilterOperationTypes.LessThanOrEquals:
{
return GenerateConditionalExpression<T>(fieldName, comparisonValue, Expression.LessThanOrEqual(
Expression.Convert(Expression.Property(e, fieldName), Expression.Constant(comparisonValue).Type),
Expression.Constant(comparisonValue)),e);
}
case FilterOperationTypes.GreaterThan:
{
return GenerateConditionalExpression<T>(fieldName, comparisonValue, Expression.GreaterThan(
Expression.Convert(Expression.Property(e, fieldName), Expression.Constant(comparisonValue).Type),
Expression.Constant(comparisonValue)), e);
}
case FilterOperationTypes.LessThan:
{
return GenerateConditionalExpression<T>(fieldName, comparisonValue, Expression.LessThan(
Expression.Convert(Expression.Property(e, fieldName), Expression.Constant(comparisonValue).Type),
Expression.Constant(comparisonValue)), e);
}
case FilterOperationTypes.NotEqual:
{
return GenerateConditionalExpression<T>(fieldName, comparisonValue, Expression.NotEqual(
Expression.Convert(Expression.Property(e, fieldName), Expression.Constant(comparisonValue).Type),
Expression.Constant(comparisonValue)), e);
}
case FilterOperationTypes.Equal:
{
return GenerateConditionalExpression<T>(fieldName, comparisonValue, Expression.Equal(
Expression.Convert(Expression.Property(e, fieldName), Expression.Constant(comparisonValue).Type),
Expression.Constant(comparisonValue)), e);
}
case FilterOperationTypes.EndsWith:
{
return GenerateGenericCallExpression<T>(fieldName, comparisonValue, "EndsWith",e);
}
case FilterOperationTypes.StartsWith:
{
return GenerateGenericCallExpression<T>(fieldName, comparisonValue, "StartsWith",e);
}
case FilterOperationTypes.NotContains:
{
return GenerateGenericCallExpression<T>(fieldName, comparisonValue, "Contains",e,false); ;
}
case FilterOperationTypes.Contains:
{
return GenerateGenericCallExpression<T>(fieldName, comparisonValue, "Contains",e);
}
default:
break;
}
return GenerateConditionalExpression<T>(fieldName, comparisonValue, Expression.Equal(
Expression.Convert(Expression.Property(e, fieldName), Expression.Constant(comparisonValue).Type),
Expression.Constant(comparisonValue)), e);
}
private Expression<Func<T, bool>> GenerateConditionalExpression<T>(string fieldName, object comparisonValue, Expression actualExpression, ParameterExpression e)
{
Expression call = Expression.Condition(Expression.Equal(e, Expression.Constant(null)),
Expression.NotEqual(e, Expression.Constant(null)),
Expression.Condition(Expression.Equal(Expression.Property(e, fieldName), Expression.Constant(null)),
Expression.NotEqual(e, Expression.Constant(null)), actualExpression));
return Expression.Lambda<Func<T, bool>>(call, e);
}
private Expression<Func<T, bool>> GenerateGenericCallExpression<T>(string fieldName, object comparisonValue, string methodName, ParameterExpression e, bool equals = true)
{
PropertyInfo propertyInfo = typeof(T).GetProperty(fieldName);
MemberExpression m = Expression.MakeMemberAccess(e, propertyInfo);
ConstantExpression c = Expression.Constant(comparisonValue, typeof(string));
MethodInfo mi = typeof(string).GetMethod(methodName, new Type[] { typeof(string) });
if (equals)
{
Expression call = Expression.Condition(Expression.Equal(e, Expression.Constant(null)),
Expression.NotEqual(e, Expression.Constant(null)),
Expression.Condition(Expression.Equal(Expression.Property(e, fieldName), Expression.Constant(null)),
Expression.NotEqual(e, Expression.Constant(null)), Expression.Call(m, mi, c)));
return Expression.Lambda<Func<T, bool>>(call, e);
}
else
{
Expression call = Expression.Condition(Expression.Equal(e, Expression.Constant(null)),
Expression.NotEqual(e, Expression.Constant(null)),
Expression.Condition(Expression.Equal(Expression.Property(e, fieldName), Expression.Constant(null)),
Expression.NotEqual(e, Expression.Constant(null)), Expression.Not(Expression.Call(m, mi, c))));
return Expression.Lambda<Func<T, bool>>(call, e);
}
}
Upvotes: -1
Reputation: 203817
You're looking for Expression.Condition
, rather than IfThenElse
, which represents the conditional operator, rather than an if/else statement. The conditional operator resolves to a value, since it's an expression, not a statement.
Upvotes: 2