【问题标题】:How to create a generic lambda from two expression trees?如何从两个表达式树创建通用 lambda?
【发布时间】:2014-02-05 13:03:13
【问题描述】:

首先,我想让您知道,在制作这个问题之前,我已经搜索了其他问题和答案,但我找不到任何可以帮助我解决我所面临的具体问题的问题。

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

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

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

我尝试使用表达式树来实现这一点,并得到了两个表达式,但现在我没有得到组合这些表达式来构建一个传递给最终的表达式

Expression.AndAlso(parentCodeFilterExpression, textFilterExpression);

这会将too表达式组合在一个中。

到目前为止,我得到的是下面显示的代码(抱歉,sn-p 太长了,但我认为这是使问题更容易理解的必要条件):

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

感谢您的任何建议。

【问题讨论】:

    标签: c# lambda expression-trees


    【解决方案1】:

    我尝试使用表达式树来实现这一点,并得到了两个表达式,但现在我没有得到组合这些表达式来构建一个传递给最终的表达式

    首先,您需要为最终(外部)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 中)。

    【讨论】:

    • “像魅力一样工作”,正如人们常说的那样。你写的第一个截图就成功了,谢谢!只是想知道:参数是用来做什么的?我查看了调试器,它成为表达式的一部分,类似于“Param_0”,但我不知道它在整个过程中的使用位置,甚至不知道它的含义。但无论如何,代码有效,问题解决了,再次感谢。只是我想知道幕后发生的事情,这样我才能真正理解任何代码的作用。
    • 一个Func&lt;,&gt;有一个参数,所以当你创建一个lambda表达式来表示一个Func&lt;,&gt;时,你需要声明一个参数来表示传入的值。
    【解决方案2】:

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

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

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

    【讨论】:

      【解决方案3】:

      您可以使用表达式上的Compile method 将Expression&lt;TDelegate&gt; 编译为TDelegate:

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

      一旦你有了它,你就可以将它用作Where 函数的参数。


      有了上面的代码,你就可以使用了

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

      【讨论】:

      • 但是如何将这两个表达式组合成一个呢?我没有在问题中提到,但是 Expression.AndAlso(parentCodeFilterExpression, textFilterExpression);不编译。
      • 此时您的代码中只有 1 个表达式。 lambdaExpression 在内部使用 GetParentCodeFilterExpression 返回的表达式,但这并不重要。
      • 是的,你是对的。如果我达到了这一点,您的代码将是解决方案,但问题是我无法使 GetLambdaExpressionForSearchByField() 方法工作。我的问题没有得到很好的解决,抱歉。我应该让它更清楚。我已经编辑了这个问题,所以也许它变得不那么混乱了。但是感谢您的回答和 cmets。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-13
      • 1970-01-01
      • 1970-01-01
      • 2023-03-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多