动态递归 lambda 表达式

发布于 2024-11-09 18:32:37 字数 2200 浏览 5 评论 0原文

我想创建动态 lambda 表达式,以便可以使用一组过滤参数来过滤列表。这就是我到目前为止所拥有的:

表达式是使用下面的方法构建的,其中 T 是

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

我然后调用的

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

列表的对象类型这对于仅包含简单类型的对象来说就像预期的那样,但如果里面有复杂类型,代码不再起作用。例如,假设我在 Example 类中有一个自定义类型 Field 的成员,并且 Field 有一个字符串属性 Value。如果 filter.PropertyName 是“Field0”(Field 类型),则代码可以正常工作,但如果我有“Field0.Value”,我会收到一个明显的错误,指出没有属性在类示例中名为“Field0.Value”。

我尝试修改表达式构建方法,如下所示:

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

但随后在编译表达式时出现 Lambda 参数不在范围内 错误。我有点明白为什么会出现此错误,但我不知道如何使其工作。

底线是我需要在形成 MemberExpression 时使表达式构建方法递归工作。我最终需要获得一个 list = list.Where(deleg).ToList(); ,它可以转换为这样的 list = list.Where(obj => obj.Field0.Value == 'something').ToList();

我刚刚开始使用表达式,所以我在这方面并不太了解,但任何帮助将不胜感激。

谢谢

I want to create dynamic lambda expressions so that I can filter a list using a set of filtering parameters. This is what I have so far:

The expression is built using the methods bellow, where T is the object type of the list

    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. For example, let's say I have a member of custom type Field inside the Example class and Field has a string property Value. 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.

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. 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. I ultimately need to obtain a list = list.Where(deleg).ToList(); that translates to something like this 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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

我很OK 2024-11-16 18:32:37

我意识到这是一篇相当老的帖子,但我遇到了完全相同的问题,并在 Mark Gravell 发布的答案中找到了一些接近的内容 此处。我只是稍微修改了一下以满足我的需求,结果如下:

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

实现:

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

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. 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);
梦里兽 2024-11-16 18:32:37

我正在尝试解决

这样我就可以使用一组过滤参数来过滤列表

而不是使用ExpressionBuilder,而是使用通用的 Filter 类。

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

首先我们需要上一些课。

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

然后我们需要一个对象列表。

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

然后让我们创建几个过滤器并将它们添加到列表中。

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

最后通过这些过滤器过滤列表

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

只有“Fafhrd”将出现在 highLevelAndPowerfulSwords 中,而 highLevelOrPowerfulSwords 将包含除“Mouse”之外的所有玩家。

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.

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

倾城°AllureLove 2024-11-16 18:32:37

看一下 ExpressionVisitor,如下所述:替换参数表达式主体中的名称

Have a look at ExpressionVisitor as described here: Replacing the parameter name in the Body of an Expression

作业与我同在 2024-11-16 18:32:37

您可以为过滤器中的每个元素生成一个表达式,并使用以下方法将它们组合成一个表达式:

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

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);
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文