How do I dynamically create an Expression<Func<MyClass, bool>> predicate from Expression<Func<MyClass, string>>?

61,982

Solution 1

It's hard to mix compiler-generated expression trees and hand-made ones, precisely because of this sort of thing - extracting out the ParameterExpressions is tricky. So let's start from scratch:

ParameterExpression argParam = Expression.Parameter(typeof(Service), "s");
Expression nameProperty = Expression.Property(argParam, "Name");
Expression namespaceProperty = Expression.Property(argParam, "Namespace");

var val1 = Expression.Constant("Modules");
var val2 = Expression.Constant("Namespace");

Expression e1 = Expression.Equal(nameProperty, val1);
Expression e2 = Expression.Equal(namespaceProperty, val2);
var andExp = Expression.AndAlso(e1, e2);

var lambda = Expression.Lambda<Func<Service, bool>>(andExp, argParam);

One important aspect I've changed is the type passed to Expression.Parameter - it certainly looks like it should be a Service rather than a string.

I've given that a try, and it seemed to work when I called lambda.Compile and executed it on a couple of sample Service objects...

Solution 2

You can create an Expression tree for nullable types, suppose you have a nullable field BoardId, you can create expression tree dynamically like this

var nameValue="BoardId=111";

you need to determine first Property type, whether its Nullable or not

Below code create a Dynamic tree expression for nullable and Non Nullable types

 public static Expression<Func<T, bool>> BuildWhereExpression<T>(string nameValueQuery ) where  T : class 
        {
            Expression<Func<T, bool>> predicate = null;
            PropertyInfo prop = null;
            var fieldName = nameValueQuery.Split("=")[0];
            var fieldValue = nameValueQuery.Split("=")[1];
            var properties = typeof(T).GetProperties();
            foreach (var property in properties)
            {
                if (property.Name.ToLower() == fieldName.ToLower())
                {
                    prop = property;
                }
            } 
            if (prop != null)
            {
                var isNullable = prop.PropertyType.IsNullableType();
                var parameter = Expression.Parameter(typeof(T), "x");
                var member = Expression.Property(parameter, fieldName); 

                if (isNullable)
                {
                    var filter1 =
                        Expression.Constant(
                            Convert.ChangeType(fieldValue, member.Type.GetGenericArguments()[0]));
                    Expression typeFilter = Expression.Convert(filter1, member.Type);
                    var body = Expression.Equal(member, typeFilter);  
                    predicate = Expression.Lambda<Func<T, bool>>(body, parameter);  
                }
                else
                {
                    if (prop.PropertyType == typeof(string) && likeOerator.ToLower() == "like")
                    {
                        var parameterExp = Expression.Parameter(typeof(T), "type");
                        var propertyExp = Expression.Property(parameterExp, prop);
                        MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
                        var someValue = Expression.Constant(fieldValue, typeof(string));
                        var containsMethodExp = Expression.Call(propertyExp, method, someValue);
                        predicate = Expression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp);
                    }
                    else
                    {
                        var constant = Expression.Constant(Convert.ChangeType(fieldValue, prop.PropertyType));
                        var body = Expression.Equal(member, constant);  
                        predicate = Expression.Lambda<Func<T, bool>>(body, parameter); `enter code here`
                    }
                }
            }
            return predicate;
        }

1- This Solution first checks for the Nullable value and generate the expression. This is How you can determine if the type is Nullable. I have created an extension method for that purpose

  public static bool IsNullableType(this Type type) {  return
    type.IsGenericType &&
    (type.GetGenericTypeDefinition().Equals(typeof(Nullable<>))); }

2- the second step is to check the type if its string then create an expression for a string.

3- the Third step is to check is value is not nullable not string then create an expression using equal

Share:
61,982

Related videos on Youtube

Torbjörn Hansson
Author by

Torbjörn Hansson

Updated on May 24, 2020

Comments

  • Torbjörn Hansson
    Torbjörn Hansson about 4 years

    I trying to append where predicates and my goal is to create the same expression as:

    Services.Where(s => s.Name == "Modules" && s.Namespace == "Namespace");
    

    I have the following code:

    Expression<Func<Service,string>> sel1 = s => s.Name;
    Expression<Func<Service,string>> sel2 = s => s.Namespace;
    
    var val1 = Expression.Constant("Modules");
    var val2 = Expression.Constant("Namespace");
    
    Expression e1 = Expression.Equal(sel1.Body, val1);
    Expression e2 = Expression.Equal(sel2.Body, val2);
    var andExp = Expression.AndAlso(e1, e2);
    
    ParameterExpression argParam = Expression.Parameter(typeof(string), "s");
    var lambda = Expression.Lambda<Func<string, bool>>(andExp, argParam);
    

    This create the following output:

    s => ((s.Name == "Modules") AndAlso (s.Namespace == "Namespace"))
    

    However, this is faulty since the parameter for Name and Namespace isn't the same. If I change one of the expression selector to:

    Expression<Func<Service,string>> sel2 = srv => srv.Namespace;
    

    The output will be:

    s => ((s.Name == "Modules") AndAlso (srv.Namespace == "Namespace"))
    

    How can I create a valid expression with use of sel1 and sel2?

    UPDATE (28 feb 2011)

    I solved it by creating invoke expressions: Expression.Invoke so the lambda expressions sel1 and sel2 don't necessary need to be a MemberExpression:

    Expression<Func<Service,string>> sel1 = s => s.Name;
    Expression<Func<Service,string>> sel2 = srv => srv.Namespace;
    
    var val1 = Expression.Constant("Modules");
    var val2 = Expression.Constant("Namespace");
    
    Expression<Func<Service, bool>> lambda = m => true;
    var modelParameter = lambda.Parameters.First();
    
    // sel1 predicate
    {
        var invokedExpr = Expression.Invoke(sel1, modelParameter);
        var binaryExpression = Expression.Equal(invokedExpr, val1);
        lambda = Expression.Lambda<Func<Service, bool>>(Expression.AndAlso(binaryExpression, lambda.Body), lambda.Parameters);
    }
    // sel2 predicate
    {
        var invokedExpr = Expression.Invoke(sel2, modelParameter);
        var binaryExpression = Expression.Equal(invokedExpr, val2);
        lambda = Expression.Lambda<Func<Service, bool>>(Expression.AndAlso(binaryExpression, lambda.Body), lambda.Parameters);
    }
    
    • Kirk Woll
      Kirk Woll over 13 years
      Have you considered PredicateBuilder? It is specifically designed to solve "trying to append where predicates". albahari.com/nutshell/predicatebuilder.aspx
    • Torbjörn Hansson
      Torbjörn Hansson over 13 years
      Sounds very interesting, I will look at that. Thank you Kirk!
  • Torbjörn Hansson
    Torbjörn Hansson over 13 years
    Thank you Jon! Got it to work now. Would it be totally wrong to get the member name of sel1 with: ((MemberExpression)sel1.Body).Member.Name; etc?
  • Jon Skeet
    Jon Skeet over 13 years
    @Torbjörn: Um, it depends. I can't really tell what you're trying to do. It would obviously only work if sel1's body really was a MemberExpression... and it might not be a property...
  • Alisson Reinaldo Silva
    Alisson Reinaldo Silva about 7 years
    It sounds redundant passing argParam inside Expression.Property(argParam, "Name") and Expression.Lambda<Func<Service, bool>>(andExp, argParam);. I think you only had to pass to both Expression.Property() (for the two properties), but why should we need to pass it again to the Expression.Lambda()? It worked anyway for my scenario, I was just curious ;)
  • Jon Skeet
    Jon Skeet about 7 years
    @Alisson: Consider what the lambda expression would look like: s => s.Name. See how s occurs in both places there? It's the same when constructing it - basically, by passing it in both places, we're trying the two uses together.
  • Alisson Reinaldo Silva
    Alisson Reinaldo Silva about 7 years
    @JonSkeet now that you said, it really makes sense. What if it was a lambda like ((a,b) => a.Name == b.Name)? Then it wouldn't make sense using the same argParam. Thanks for your clarification.