通过表达式树构建表达式列表

时间:2015-11-20 22:04:32

标签: c# linq expression-trees

我有以下方法,它根据列名和值构建表达式:

public Func<TSource, bool> SimpleComparison<TSource>(string property, object value)
{
    var type = typeof (TSource);
    var pe = Expression.Parameter(type, "p");
    var propertyReference = Expression.Property(pe, property);
    var constantReference = Expression.Constant(value);
    return Expression.Lambda<Func<TSource, bool>>
        (Expression.Equal(propertyReference, constantReference),
        new[] { pe }).Compile();
}

我正在尝试创建表达式列表,然后使用最终的连接/编译表达式查询我的数据源。

我曾尝试使用Expression.Block但这个概念对我来说有点难以掌握。我也看过Expression.Loop,但我不确定这是否是我需要在这里使用的。

理想情况下,我可以做这样的事情:

var filters = request.Filter.Filters;

IQueryable<MyDTO> myDataSource = context.MyDataSource;

var expressions = null;

foreach (var filter in filters)
{

    expressions.Add(SimpleExpression<MyDTO>(filter.Name, filter.Value));

}

return myDataSource.Where(expressions);

关于如何做这样的事情的任何想法?

3 个答案:

答案 0 :(得分:4)

你过度思考问题。你根本不需要结合你的表达。唯一复杂的部分是实施SimpleComparison,但您已经完成了这项工作。好吧,主要是。您应该返回Expression<Func<...>>,而不是Func<...>,因此它应该是

public Expression<Func<TSource, bool>> SimpleComparison<TSource>(string property, object value)
{
    // ...
    return Expression.Lambda<Func<TSource, bool>>
        (Expression.Equal(propertyReference, constantReference),
        new[] { pe });
}

完成后,您可以反复调用Where来链接过滤器,如下所示:

var filters = request.Filter.Filters;
IQueryable<MyDTO> query = context.MyDataSource;
foreach (var filter in filters)
    query = query.Where(SimpleComparison<MyDTO>(filter.Name, filter.Value));
return query;

答案 1 :(得分:2)

我最近遇到了您的确切问题,当尝试使用&&或||组合多个表达式时,以下解决方案对我有用:

    public Expression<Func<TSource, bool>> SimpleComparison<TSource>(List<QueryFilterObject> queryFilterObjects)
{
        //initialize the body expression
        BinaryExpression bodyExpression = null;
        BinaryExpression andExpressionBody = null;
        BinaryExpression orExpressionBody = null;

        //create parameter expression
        ParameterExpression parameterExpression = Expression.Parameter(typeof(TSource), "DynamicFilterQuery");

        //list of binary expressions to store either the || or && operators
        List<BinaryExpression> andExpressions = new List<BinaryExpression>();
        List<BinaryExpression> orExpressions = new List<BinaryExpression>();

        foreach (var queryFilterObject in queryFilterObjects)
        {
            //create member property expression
            var property = Expression.Property(parameterExpression, queryFilterObject.PropertyName);

            //create the constant expression value
            var constantExpressionValue = Expression.Constant(queryFilterObject.Value, queryFilterObject.PropertyType);

            //create the binary expression clause based on the comparison operator
            BinaryExpression clause = null;
            if (queryFilterObject.ComparisonOperator == "==")
            {
                clause = Expression.Equal(property, constantExpressionValue);
            }
            else if (queryFilterObject.ComparisonOperator == "!=")
            {
                clause = Expression.NotEqual(property, constantExpressionValue);
            }
            else if (queryFilterObject.ComparisonOperator == ">")
            {
                clause = Expression.GreaterThan(property, constantExpressionValue);
            }
            else if (queryFilterObject.ComparisonOperator == ">=")
            {
                clause = Expression.GreaterThan(property, constantExpressionValue);
            }
            else if (queryFilterObject.ComparisonOperator == "<")
            {
                clause = Expression.LessThan(property, constantExpressionValue);
            }
            else if (queryFilterObject.ComparisonOperator == "<=")
            {
                clause = Expression.LessThanOrEqual(property, constantExpressionValue);
            }

            //you should validate against a null clause....

            //assign the item either to the relevant logical comparison expression list
            if (queryFilterObject.LogicalOperator == "and" || queryFilterObject.LogicalOperator == "&&")
            {
                andExpressions.Add(clause);

            }
            else if (queryFilterObject.LogicalOperator == "or" || queryFilterObject.LogicalOperator == "||")
            {
                orExpressions.Add(clause);

            }

        }

        if (andExpressions.Count > 0)
            andExpressionBody = andExpressions.Aggregate((e, next) => Expression.And(e, next));

        if (orExpressions.Count > 0)
            orExpressionBody = orExpressions.Aggregate((e, next) => Expression.Or(e, next));

        if (andExpressionBody != null && orExpressionBody != null)
            bodyExpression = Expression.OrElse(andExpressionBody, orExpressionBody);

        if (andExpressionBody != null && orExpressionBody == null)
            bodyExpression = andExpressionBody;

        if (andExpressionBody == null && orExpressionBody != null)
            bodyExpression = orExpressionBody;

        if (bodyExpression == null)
            throw new Exception("Null Expression.");

        var finalExpression = Expression.Lambda<Func<WorkOrder, bool>>(bodyExpression, parameterExpression);

        return finalExpression;

}

public class QueryFilterObject
{
    public string PropertyName { get; set; }

    public Type PropertyType { get; set; }

    public object Value { get; set; }

    public string ComparisonOperator { get; set; }

    public string LogicalOperator { get; set; }


}

就我而言,我返回的是func(Expression<Func<TSource, bool>>)的表达式,而不是func本身(Func<TSource, bool>)的表达式。这使我的where子句可以作为Iqueryable保留,否则返回 Func<TSource, bool> 而不是*Expression<Func<TSource, bool>>*会使您的where子句变成可枚举的对象。

最后,我只是像下面这样调用表达式:

IQueryable<MyDTO> myDataSource = context.MyDataSource;

var filter = SimpleComparison(queryFilterObjects);

if (filter != null)
            myDataSource = myDataSource.Where(filter);

//perfom other operations such as order by 

return myDataSource.ToList();

答案 2 :(得分:1)

您可以将函数更改为返回已编译的lambda,而不是返回基表达式。一旦将这些表达式存储在列表中,就可以使用Linq Aggregate()函数构建最终谓词。

public Func<TSource, bool> CreatePredicate<TSource>(IEnumerable<Expression> expressions)
{
    var parameter = Expression.Parameter(typeof(TSource), "x"); 
    var body = expressions.Aggregate((e, next) => Expression.AndAlso(e, next));
    var predicate = Expression.Lambda<Func<TSource, bool>>(body, parameter).Compile();

    return predicate;
}

这假设您希望使用&&加入所有语句。如果您希望使用||加入Expression.OrElse,请使用Expression.AndAlso代替var filters = request.Filter.Filters; IQueryable<MyDTO> myDataSource = context.MyDataSource; var expressions = new List<Expression>(); foreach (var filter in filters) { expressions.Add(SimpleComparison<MyDTO>(filter.Name, filter.Value)); } var predicate = CreatePredicate<MyDTO>(expressions); return myDataSource.Where(predicate); 。如果混合使用过滤器和过滤器,则解决方案会变得相当复杂。

上面的例子将成为

if (head == NULL || strcmp( songName, head -> songName ) < 0 ) {