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

52

我试图添加 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);
}

3
你考虑过 PredicateBuilder 吗?它是专门设计用来解决“尝试添加 where 谓词”的问题。http://www.albahari.com/nutshell/predicatebuilder.aspx - Kirk Woll
听起来非常有趣,我会去看一下。谢谢你,Kirk! - Torbjörn Hansson
2个回答

90

混合使用编译器生成的表达式树和手写的表达式树很困难,正是因为这种情况——提取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对象上执行它时,它似乎工作正常...


谢谢Jon!现在已经解决了。使用以下代码获取sel1的成员名称是否完全错误: ((MemberExpression)sel1.Body). Member.Name;等? - Torbjörn Hansson
@Torbjörn:嗯,这取决于。我真的不知道你想做什么。显然,只有当sel1的主体确实是一个“ MemberExpression”时才会起作用...而它可能不是一个属性... - Jon Skeet
Expression.Property(argParam, "Name")Expression.Lambda<Func<Service, bool>>(andExp, argParam); 中传递 argParam 似乎有些冗余。我认为只需对两个属性中的 Expression.Property() 传递一次即可,但为什么我们还需要再传递一次给 Expression.Lambda() 呢?不过在我的场景中它也能正常工作,只是我感到好奇 ;) - Alisson Reinaldo Silva
1
@Alisson:考虑一下lambda表达式会是什么样子:s => s.Name。看到s在这两个地方都出现了吗?构造时也是一样的 - 基本上,通过在这两个地方传递它,我们正在尝试将这两个用法结合在一起。 - Jon Skeet
@JonSkeet 现在你这么说,真的很有道理。如果它是一个像 ((a,b) => a.Name == b.Name) 这样的 lambda 表达式呢?那么使用相同的 argParam 就没有意义了。感谢您的澄清。 - Alisson Reinaldo Silva

6

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

var nameValue="BoardId=111";

你需要先确定属性类型是否可空

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

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

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

第二步是检查该类型是否为字符串,如果是,则为字符串创建表达式。

第三步是检查值是否非空且不为字符串,然后使用等于符号创建表达式。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接