如何从 Expression<Func<MyClass, string>> 动态创建 Expression<Func<MyClass, bool>> 谓

Posted

技术标签:

【中文标题】如何从 Expression<Func<MyClass, string>> 动态创建 Expression<Func<MyClass, bool>> 谓词?【英文标题】:How do I dynamically create an Expression<Func<MyClass, bool>> predicate from Expression<Func<MyClass, string>>? 【发布时间】:2011-07-02 22:38:22 【问题描述】:

我尝试添加 where 谓词,我的目标是创建与以下相同的表达式:

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

我有以下代码:

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

这将创建以下输出:

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

但是,这是错误的,因为 NameNamespace 的参数不一样。如果我将表达式选择器之一更改为:

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

输出将是:

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

如何使用 sel1sel2 创建有效的表达式?

更新(2011 年 2 月 28 日)

我通过创建调用表达式解决了这个问题:Expression.Invoke,因此 lambda 表达式 sel1 和 sel2 不必是 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);

【问题讨论】:

你考虑过 PredicateBuilder 吗?它是专门为解决“试图追加 where 谓词”而设计的。 albahari.com/nutshell/predicatebuilder.aspx 听起来很有趣,我会看看。谢谢柯克! 【参考方案1】:

很难将编译器生成的表达式树和手工制作的表达式树混合在一起,正是因为这种事情 - 提取 ParameterExpressions 是很棘手的。所以让我们从头开始:

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

我更改的一个重要方面是传递给 Expression.Parameter 的类型 - 它确实看起来应该是 Service 而不是 string

我已经尝试过了,当我调用 lambda.Compile 并在几个示例 Service 对象上执行它时,它似乎有效...

【讨论】:

谢谢乔恩!现在可以开始工作了。用: ((MemberExpression)sel1.Body).Member.Name; 获取 sel1 的成员名称是完全错误的吗?等等? @Torbjörn:嗯,这取决于。我真的不知道你想做什么。显然,只有sel1 的主体真的 MemberExpression...并且它可能不是属性... Expression.Property(argParam, "Name")Expression.Lambda&lt;Func&lt;Service, bool&gt;&gt;(andExp, argParam); 中传递argParam 听起来是多余的。我认为你只需要传递给Expression.Property()(对于这两个属性),但是为什么我们需要再次传递给Expression.Lambda()?无论如何,它对我的​​场景有效,我只是好奇;) @Alisson:考虑一下 lambda 表达式的样子:s =&gt; s.Name。看看s 是如何在这两个地方出现的?构造它时是一样的——基本上,通过在两个地方传递它,我们正在尝试两种用途。 @JonSkeet 既然你说过,这真的很有意义。如果它是像 ((a,b) =&gt; a.Name == b.Name) 这样的 lambda 怎么办?那么使用相同的argParam 就没有意义了。感谢您的澄清。【参考方案2】:

您可以为可空类型创建表达式树,假设您有一个可空字段 BoardId,您可以像这样动态创建表达式树

var nameValue="BoardId=111";

您需要确定第一个属性类型,是否为 Nullable

以下代码为可空和不可空类型创建动态树表达式

 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- 此解决方案首先检查 Nullable 值并生成表达式。 这是您如何确定类型是否为 Nullable 的方法。为此,我创建了一个扩展方法

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

2-第二步是检查类型,如果它的字符串,然后为字符串创建一个表达式。

3-第三步是检查值是不是可以为空的不是字符串然后使用等于创建一个表达式

【讨论】:

以上是关于如何从 Expression<Func<MyClass, string>> 动态创建 Expression<Func<MyClass, bool>> 谓的主要内容,如果未能解决你的问题,请参考以下文章

Func<T> 如何隐式转换为 Expression<Func<T>>?

从 Expression<Func<T, bool>> 转换为字符串

从 Expression<Func<TModel,TProperty>> 以字符串形式获取属性

AndAlso 在几个 Expression<Func<T, bool>> 之间:从范围引用

如何动态创建 Expression<Func<MyClass, bool>> 谓词?

如何从属性名称创建 Expression<Func<TModel, string>> 表达式