如何从两个表达式树创建一个通用的lambda?

时间:2014-02-05 13:03:13

标签: c# lambda expression-trees

首先,我想让你知道我在制作这个问题之前已经搜索了另一个问题和答案,但我找不到任何能帮助我解决这个问题的问题。

我需要根据其类的两个属性过滤寄存器,其中一个是与搜索对应的字段,另一个是必须在数据库中引用寄存器的另一个实体的数字代码。 / p>

我的搜索功能具有以下签名:

public List<TView> SearchByField(int parentCode, string fieldName, string filter);

我尝试使用Expression Trees实现这一点,并让它获得两个表达式,但现在我没有将这些表达式组合起来构建一个传递给最终的

Expression.AndAlso(parentCodeFilterExpression, textFilterExpression);

只将一个表达式组合在一起。

到目前为止,我得到的代码是下面显示的代码(对于长片段感到抱歉,但我认为这对于更容易理解这个问题是必要的):

public List<TView> SearchPerField(int parentCode, string fieldName, string filter)
    {
        var lambdaExpression = GetLambdaExpressionForSearchByField(fieldName, filter, parentCode);

        return new PersistenciaImpl<TView>().Where(lambdaExpression).ToList();
    }

    private Expression<Func<TView, bool>> GetLambdaExpressionForSearchByField(string fieldName, string filter, int parentCode)
    {
        Expression<Func<TView, bool>> textFilterExpression = GetTextFilterExpression(fieldName, filter);

        Expression<Func<TView, bool>> parentCodeFilterExpression = GetParentCodeFilterExpression(parentCode);

        Expression.Lambda<Func<TView, bool>>(textFilterExpression, parentCodeFilterExpression);

        // THIS IS THE POINT. HOW TO MAKE THIS WORK?
        Expression.AndAlso(parentCodeFilterExpression, textFilterExpression);

        return textFilterExpression;
    }

    private Expression<Func<TView, bool>> GetParentCodeFilterExpression(int parentCode)
    {
        ParameterExpression parameter = Expression.Parameter(typeof(TView), "x");

        Expression parent = Expression.Property(parameter, "Parent");

        Expression parentCodeExpression = Expression.Property(parent, "Code");

        Expression target = Expression.Constant(parentCode);

        Expression containsMethod = Expression.Call(parentCodeExpression, "Equals", null, target);

        Expression<Func<TView, bool>> textFilterExpression =
           Expression.Lambda<Func<TView, bool>>(containsMethod, parameter);

        return textFilterExpression;
    }

    private Expression<Func<TView, bool>> GetTextFilterExpression(string fieldName, string filter)
    {
        ParameterExpression parameter = Expression.Parameter(typeof(TView), "x");

        Expression property = Expression.Property(parameter, fieldName);

        Expression target = Expression.Constant(filter.ToUpper());

        Expression containsMethod = Expression.Call(property, "Contains", null, target);

        Expression<Func<TView, bool>> textFilterExpression =
           Expression.Lambda<Func<TView, bool>>(containsMethod, parameter);

        return textFilterExpression;
    }

感谢您的任何建议。

3 个答案:

答案 0 :(得分:0)

您可以使用表达式上的Compile methodExpression<TDelegate>编译为TDelegate

Expression<Func<TView, bool>> lambdaExpression =
    GetLambdaExpressionForSearchByField(fieldName, filter, parentCode);
Func<TView, bool> func = lambdaExpression.Compile();

完成后,您可以将其用作Where函数的参数。


使用上面的代码,您可以使用

return new PersistenciaImpl<TView>().Where(func).ToList();

答案 1 :(得分:0)

我认为你需要这样的东西:

MethodCallExpression where = Expression.Call((
            typeof(Queryable), 
            "Where", 
            new Type[] { TView },  
            lambdaExpression );

请注意,我不认为这是一个解决方案;它只是一个想法或例子。也许this link会帮助你。

答案 2 :(得分:0)

  

我尝试使用Expression Trees实现这一点,并让它获得两个表达式,但现在我没有将这些表达式组合起来构建一个传递给最终的

首先,您需要为最终(外部)lambda声明一个参数。然后你需要独立地调用你的两个过滤器(内部)lambda,并将相同的参数传递给每个:

// using E = System.Linq.Expressions.Expression;

var item = E.Parameter(typeof(TView));

var combined = E.Lambda<Func<TView, bool>>(
    E.AndAlso(
        E.Invoke(parentCodeFilterExpression, item), 
        E.Invoke(textFilterExpression, item)),
    item);

如果您需要这些表达式与Entity Framework等查询提供程序兼容,那么事情会变得有点混乱,因为可能不支持Invoke表达式。你必须手动内联两个过滤器lambda,它需要遍历每个过滤器的主体,并用对外部lambda参数的引用替换内部参数引用:

// using E = System.Linq.Expressions.Expression;

sealed class ParameterReplacementVisitor : ExpressionVisitor
{
    private readonly IDictionary<E, E> _replacements;

    public ParameterReplacementVisitor(IDictionary<E, E> replacements)
    {
        _replacements = replacements;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        E replacement;

        if (_replacements.TryGetValue(node, out replacement))
            return this.Visit(replacement);

        return base.VisitParameter(node);
    }
}

// ...

var item = E.Parameter(typeof(TView));

var visitor = new ParameterReplacementVisitor(
    new Dictionary<E, E> {
        { parentCodeFilterExpression.Parameters[0], item },
        { textFilterExpression.Parameters[0], item }
    }
);

var combined = E.Lambda<Func<TView, bool>>(
    E.AndAlso(
        visitor.Visit(parentCodeFilterExpression.Body), 
        visitor.Visit(textFilterExpression.Body)), 
    item);

或者,如果你在帖子建议的情况下在封闭环境中编写内部表达式,你可以简单地将外部lambda参数作为参数传递给构造内部表达式的方法,并仅返回实体(不要打扰在lambdas中包裹内部过滤器。)