简体   繁体   English

动态递归 lambda 表达式

[英]Dynamic recursive lambda expressions

I want to create dynamic lambda expressions so that I can filter a list using a set of filtering parameters.我想创建动态 lambda 表达式,以便我可以使用一组过滤参数过滤列表。 This is what I have so far:这是我到目前为止所拥有的:

The expression is built using the methods bellow, where T is the object type of the list表达式是使用下面的方法构建的,其中 T 是列表的 object 类型

    public static Expression<Func<T, bool>> GetExpression<T>(IList<DynamicFilter> filters)
    {
        if (filters.Count == 0)
            return null;

        ParameterExpression param = Expression.Parameter(typeof(T), "t");
        Expression exp = null;

        if (filters.Count == 1)
            exp = GetExpression<T>(param, filters[0]);

        [...]

        return Expression.Lambda<Func<T, bool>>(exp, param);
    }

    private static Expression GetExpression<T>(ParameterExpression param, DynamicFilter filter)
    {
        MemberExpression member = Expression.Property(param, filter.PropertyName);
        ConstantExpression constant = Expression.Constant(filter.Value);

        [...]

        return Expression.Call(member, filterMethod, constant);
    }

I then call然后我打电话

List<Example> list = ...;
var deleg = ExpressionBuilder.GetExpression<Example>(dynFiltersList).Compile();
list = list.Where(deleg).ToList();

This works just as expected with an object that contains only simple types, but if there are complex types inside, the code doesn't work anymore.这与仅包含简单类型的 object 的预期相同,但如果内部有复杂类型,则代码将不再工作。 For example, let's say I have a member of custom type Field inside the Example class and Field has a string property Value.例如,假设我在示例 class 中有一个自定义类型字段的成员,并且字段有一个字符串属性值。 If filter.PropertyName would be 'Field0' (of type Field), the code would work just fine, but if I have 'Field0.Value' I would get an obvious error stating that there is no property named 'Field0.Value' inside class Example.如果filter.PropertyName是“Field0”(字段类型),代码可以正常工作,但如果我有“Field0.Value”,我会收到一个明显的错误,说明里面没有名为“Field0.Value”的属性class 示例。

I tried modifying the expression building method, like this:我尝试修改表达式构建方法,如下所示:

        MemberExpression member = null;
        if (filter.PropertyName.Contains('.'))
        {
            string[] props = filter.PropertyName.Split('.');

            ParameterExpression param1 = Expression.Parameter(typeof(T).GetProperty(props[0]).PropertyType, "t1");
            member = Expression.Property(param1, props[0]);
        }
        else
        {
            member = Expression.Property(param, filter.PropertyName);
        }

but then I got a Lambda parameter not in scope error when compiling the expression.但后来我在编译表达式时得到一个Lambda parameter not in scope错误中。 I sort of understand why I get this error, but I don't know how to make this work.我有点理解为什么我会收到这个错误,但我不知道如何使它工作。

Bottom line is I need to make the expression building method work recursively when forming the MemberExpression.底线是我需要在形成 MemberExpression 时使表达式构建方法递归工作。 I ultimately need to obtain a list = list.Where(deleg).ToList();我最终需要获得一个list = list.Where(deleg).ToList(); that translates to something like this list = list.Where(obj => obj.Field0.Value == 'something').ToList();翻译成这样的东西list = list.Where(obj => obj.Field0.Value == 'something').ToList();

I've just started working with expressions so I don't really know too much in this area, but any help would be appreciated.我刚刚开始使用表达式,所以我对这方面的了解并不多,但我们将不胜感激。

Thanks谢谢

I realize this is a fairly old post, but I had the exact same problem and found something close in an answer that Mark Gravell posted here .我意识到这是一篇相当老的帖子,但我遇到了完全相同的问题,并在 Mark Gravell 在此处发布的答案中找到了一些接近的东西。 I just modified it slightly to meet my needs and below is the result:我只是稍微修改了一下以满足我的需要,结果如下:

    private Expression GetDeepProperty(Expression parameter, string property)
    {
        var props = property.Split('.');
        var type = parameter.Type;

        var expr = parameter;
        foreach (var prop in props)
        {
            var pi = type.GetProperty(prop);
            expr = Expression.Property(expr, pi);
            type = pi.PropertyType;
        }

        return expr;
    }

Implementation:执行:

var method = typeof (string).GetMethod("Contains", new Type[] {typeof (string)}, null);
var lambdaParameter = Expression.Parameter(typeof(TEntity), "te");
var filterExpression = Expression.Lambda<Func<TEntity, bool>> (
            filters.Select(filter => Expression.Call(GetDeepProperty(lambdaParameter, filter.Property),
                                                      method,
                                                      Expression.Constant(filter.Value))).
                Where(exp => exp != null).
                Cast<Expression>().
                ToList().
                Aggregate(Expression.Or), lambdaParameter);

Have a look at ExpressionVisitor as described here: Replacing the parameter name in the Body of an Expression查看 ExpressionVisitor,如下所述: 替换表达式正文中的参数名称

I'm trying to address我正在尝试解决

so that I can filter a list using a set of filtering parameters这样我就可以使用一组过滤参数过滤列表

not by using ExpressionBuilder but instead by using a generic Filter class.不是使用ExpressionBuilder ,而是使用通用过滤器 class。

public class Filter<T> where T: class
{
    private readonly Predicate<T> criteria;

    public Filter(Predicate<T> criteria)
    {
        this.criteria = criteria;
    }

    public bool IsSatisfied(T obj)
    {
        return criteria(obj);
    }
}

First we need to have some classes.首先我们需要有一些类。

public class Player
{
    public string Name { get; set; }
    public int Level { get; set; }
    public enum Sex { Male, Female, Other };
    public Weapon Weapon { get; set; }
}

public class Weapon
{
    public string Name { get; set; }
    public int MaxDamage { get; set; }
    public int Range { get; set; }
    public WeaponClass Class { get; set; }

    public enum WeaponClass { Sword, Club, Bow }
}

Then we need a list of objects.然后我们需要一个对象列表。

var graywand = new Weapon { Name = "Graywand", MaxDamage = 42, Range = 1, Class = Weapon.WeaponClass.Sword };
var scalpel = new Weapon { Name = "Scalpel", MaxDamage = 33, Range = 1, Class = Weapon.WeaponClass.Sword };
var players = new List<Player> {
    new Player { Name = "Fafhrd", Level = 19, Weapon = graywand }, 
    new Player { Name = "Gray Mouser", Level = 19, Weapon = scalpel }, 
    new Player { Name = "Freddy", Level = 9, Weapon = graywand }, 
    new Player { Name = "Mouse", Level = 8, Weapon = scalpel} 
};

Then let's create a couple of filters and add those to a list.然后让我们创建几个过滤器并将它们添加到列表中。

var powerfulSwords = new Filter<Player>(p => p.Weapon.MaxDamage>35);
var highLevels = new Filter<Player>(p => p.Level>15);

var filters = new List<Filter<Player>>();
filters.Add(powerfulSwords);
filters.Add(highLevels);

Finally filter the list by those filters最后通过这些过滤器过滤列表

var highLevelAndPowerfulSwords = players.Where(p => filters.All(filter => filter.IsSatisfied(p)));
var highLevelOrPowerfulSwords = players.Where(p => filters.Any(filter => filter.IsSatisfied(p)));

Only "Fafhrd" will be in highLevelAndPowerfulSwords and highLevelOrPowerfulSwords will contain all players but "Mouse".只有“Fafhrd”将在highLevelAndPowerfulSwords中, highLevelOrPowerfulSwords将包含除“Mouse”之外的所有玩家。

You could generate an expression for each element in filter and combine them into one single expression using the methods below:您可以为过滤器中的每个元素生成一个表达式,并使用以下方法将它们组合成一个表达式:

    public static Expression<Func<T, K>> CombineAnd<T, K>(Expression<Func<T, K>> a, Expression<Func<T, K>> b)
    {
        ParameterExpression firstParameter = a.Parameters.First();
        Expression<Func<T, K>> b1 = Expression.Lambda<Func<T, K>>(Expression.Invoke(b, firstParameter), firstParameter);
        return Expression.Lambda<Func<T, K>>(Expression.And(a.Body, b1.Body), firstParameter);
    }
    public static Expression<Func<T, K>> CombineOr<T, K>(Expression<Func<T, K>> a, Expression<Func<T, K>> b)
    {
        ParameterExpression firstParameter = a.Parameters.First();
        Expression<Func<T, K>> b1 = Expression.Lambda<Func<T, K>>(Expression.Invoke(b, firstParameter), firstParameter);
        return Expression.Lambda<Func<T, K>>(Expression.Or(a.Body, b1.Body), firstParameter);
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM