Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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); } 
like image 994
Torbjörn Hansson Avatar asked Feb 23 '11 17:02

Torbjörn Hansson


People also ask

How do you combine expressions in C#?

You can use Expression. AndAlso / OrElse to combine logical expressions, but you have to make sure the ParameterExpressions are the same. This solution was the only one that allowed me to have x => x. Property == Value combined with arg => arg.

What is tree expressions C#?

Expression trees represent code in a tree-like data structure, where each node is an expression, for example, a method call or a binary operation such as x < y . You can compile and run code represented by expression trees.


2 Answers

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

like image 42
Aamir Avatar answered Oct 11 '22 17:10

Aamir


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...

like image 105
Jon Skeet Avatar answered Oct 11 '22 18:10

Jon Skeet