构建动态表达式树以过滤集合属性

Posted

技术标签:

【中文标题】构建动态表达式树以过滤集合属性【英文标题】:Building a dynamic expression tree to filter on a collection property 【发布时间】:2012-02-17 04:07:52 【问题描述】:

我正在尝试构建一个 lambda 表达式,它将与其他表达式组合成一个相当大的表达式树进行过滤。在我需要按子集合属性过滤之前,这工作正常。

如何构建一个 Lambda 表达式,该表达式将使用 Any() 对作为根对象的属性的集合的属性进行过滤?

例子:

CurrentDataSource.Offices.Where(o => o.base_Trades.Any(t => t.Name == "test"))

这就是我静态构建表达式的方式,但我需要动态构建它。很抱歉造成混乱。

编辑:这是我如何处理不太复杂的表达式的 sn-p:

IQueryable<Office> officeQuery = CurrentDataSource.Offices.AsQueryable<Office>();
ParameterExpression pe = Expression.Parameter(typeof(Office), "Office");
ParameterExpression tpe = Expression.Parameter(typeof(Trades), "Trades");

Expression SimpleWhere = null;
Expression ComplexWhere = null;
foreach (ServerSideFilterObject fo in ssfo)

    SimpleWhere = null;
    foreach (String value in fo.FilterValues)
    
        if (!CollectionProperties.Contains(fo.PropertyName))
        
            //Handle singleton lambda logic here.
            Expression left = Expression.Property(pe, typeof(Office).GetProperty(fo.PropertyName));
            Expression right = Expression.Constant(value);
            if (SimpleWhere == null)
            
                SimpleWhere = Expression.Equal(left, right);
            
            else
            
                Expression e1 = Expression.Equal(left, right);
                SimpleWhere = Expression.Or(SimpleWhere, e1);
            
        
        else
        
            //handle inner Collection lambda logic here.
            Expression left = Expression.Property(tpe, typeof(Trades).GetProperty("Name"));
            Expression right = Expression.Constant(value);
            Expression InnerLambda = Expression.Equal(left, right);

            //Problem area.
            Expression OfficeAndProperty = Expression.Property(pe, typeof(Office).GetProperty(fo.PropertyName));
            Expression OuterLambda = Expression.Call(OfficeAndProperty, typeof(Trades).GetMethod("Any", new Type[]  typeof(Expression)  ),InnerLambda);

            if (SimpleWhere == null)
                SimpleWhere = OuterLambda;
            else
                SimpleWhere = Expression.Or(SimpleWhere, OuterLambda);
        
    
    if (ComplexWhere == null)
        ComplexWhere = SimpleWhere;
    else
        ComplexWhere = Expression.And(ComplexWhere, SimpleWhere);

MethodCallExpression whereCallExpression = Expression.Call(typeof(Queryable), "Where", new Type[]  officeQuery.ElementType , officeQuery.Expression, Expression.Lambda<Func<Office, bool>>(ComplexWhere, new ParameterExpression[]  pe ));
results = officeQuery.Provider.CreateQuery<Office>(whereCallExpression);

【问题讨论】:

你是在问如何构建表达式树? 我不确定层次结构在您的示例中是如何工作的。你能详细说明一下吗?办公室是根,然后每个办公室都有一个交易集合吗?你想过滤交易的名称??过滤器是我有点迷路的地方。对不起。 不,我只是不确定用于构建具有内部方法调用和参数表达式的表达式的语法。在这种情况下,我收到一条错误消息,指出找不到 Any(),因为我的参数与定义不匹配。在这种情况下,我不确定这是因为我对语法不感兴趣,还是我使用它的方式不支持 Any()。 就层次结构而言:办公室是根,每个办公室都有一个交易集合。我正在尝试根据集合中每个交易对象的 Name 属性过滤集合。 【参考方案1】:

找到了解决方案。我之前没有在正确的地方寻找任何方法。

Expression left = Expression.Property(tpe, typeof(Trades).GetProperty("Name"));
Expression right = Expression.Constant(value);
Expression InnerLambda = Expression.Equal(left, right);
Expression<Func<Trades, bool>> innerFunction = Expression.Lambda<Func<Trades, bool>>(InnerLambda, tpe);

method = typeof(Enumerable).GetMethods().Where(m => m.Name == "Any" && m.GetParameters().Length == 2).Single().MakeGenericMethod(typeof(Trades));
OuterLambda = Expression.Call(method, Expression.Property(pe, typeof(Office).GetProperty(fo.PropertyName)),innerFunction);

【讨论】:

你拯救了我的一天,@George【参考方案2】:

请不要这样做,你真正希望它使用一个名为动态 linq 的库。 http://nuget.org/packages/DynamicLINQ

您可以将查询存储为字符串,它支持非常复杂的查询。表达式树是一场噩梦。

【讨论】:

这是一个很棒的库,我也使用它,但它不支持例如对 myIQueryable.OrderBy(x =&gt; x.MyCollection.Select(y =&gt; y.Myproperty)) 之类的集合属性的属性进行排序,至少我无法让它工作【参考方案3】:

您作为示例列出的内容将根据您的评论起作用。这是我使用的一个示例:

Templates.Where(t => t.TemplateFields.Any(f => f.Required == 'Y'))

我们有包含特定字段集合的模板,这些字段可能是必需的。所以我可以得到上面那个语句需要任何字段的模板。

希望这会有所帮助……或者至少可以确认您正在尝试做什么。如果您对此有更多疑问,请告诉我,我会详细说明。

祝你好运!

【讨论】:

这与我正在使用的类似,但我必须使用反射动态构建 lamda 表达式,以便我可以确保过滤器包含集合中的其他过滤器。【参考方案4】:

提供的代码

CurrentDataSource.Offices.Where(o => o.base_Trades.Any(t => t.Name == "test"))

应该可以工作,只要o.base_Trades 实现IEnumerable&lt;Trade&gt;。如果o.base_Trades 只实现IEnumerable,则需要使用Cast&lt;Trade&gt;(),如果您可以确定o.base_Trades 中的所有元素都属于您的Trade 类型或OfType&lt;Trade&gt;(),如果可能存在其他(不兼容的)类型。

然后看起来像这样:

CurrentDataSource.Offices
    .Where(o => o.base_Trades.Cast<Trade>.Any(t => t.Name == "test"))

【讨论】:

以上是关于构建动态表达式树以过滤集合属性的主要内容,如果未能解决你的问题,请参考以下文章

如何创建 LINQ 表达式树以选择匿名类型

是否可以解释 C# 表达式树以发出 JavaScript?

如何创建表达式树以执行与“StartsWith”相同的操作

改变谓词的表达式树以针对另一种类型

[C#] 说说表达式树 - Expression Trees

表达式树 Expression Trees