Ortiga
Ortiga

Reputation: 8814

Construct expression tree with chained properties?

I have a method that accepts Expression<Func<T, string>>, for example x => x.Name, and a term, and returns x => x.Name.Contains(term):

Given the model;

class X
{
    public Y Y {get; set;}
}

class Y
{
    public string Z {get; set;}
}

It works well for GenerateForMember<Y>(y => y.Z, "foobar"), but currently don't work for GenerateForMember<X>(x => x.Y.Z, "foobar"). It gives the exception

'Z' is not a member of 'UserQuery+X'

How to I update my method to work with chained properties?

Method is as follows:

protected Expression<Func<T, bool>> GenerateForMember<T>(Expression<Func<T,string>> expression, string term)
{
    var type = typeof(T);

    var memberExpression = ((expression.Body.NodeType == ExpressionType.Convert) 
        ? ((UnaryExpression)expression.Body).Operand 
        : expression.Body) as MemberExpression;

    ParameterExpression parameter = Expression.Parameter(type, type.Name.ToLower());
    MemberExpression member = Expression.PropertyOrField(parameter, memberExpression.Member.Name);

    var propertyInfo = memberExpression.Member as PropertyInfo;

    var memberType = propertyInfo == null
        ? ((FieldInfo) memberExpression.Member).FieldType
        : propertyInfo.PropertyType;


    ConstantExpression constant = Expression.Constant(term, typeof(string));

    MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
    var containsMethodExp = Expression.Call(member, method, constant);

    return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameter);
}

Upvotes: 0

Views: 117

Answers (2)

Yacoub Massad
Yacoub Massad

Reputation: 27861

Try this:

protected Expression<Func<T, bool>> GenerateForMember<T>(Expression<Func<T, string>> expression, string term)
{
    ConstantExpression constant = Expression.Constant(term, typeof(string));

    MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });

    var containsMethodExp = Expression.Call(expression.Body, method, constant);

    return Expression.Lambda<Func<T, bool>>(containsMethodExp, expression.Parameters[0]);
}

Upvotes: 1

Olivier Jacot-Descombes
Olivier Jacot-Descombes

Reputation: 112362

You are dissecting the original expression and later re-construct it again. This is not necessary. You can use expression.Body directly in order to create the method call. Like this, it should work with any lambda expression.

var type = typeof(T);

ParameterExpression parameter = Expression.Parameter(type, type.Name.ToLower());

ConstantExpression constant = Expression.Constant(term, typeof(string));

MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var containsMethodExp = Expression.Call(expression.Body, method, constant);

return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameter);

Upvotes: 2

Related Questions