如何为表达式添加另一个条件?

时间:2014-02-26 11:19:33

标签: c# linq

我有一个这样的表达式:

Expression<Func<int, bool>> exp = i => i<15 && i>10;

我想在此行之后向exp添加条件。我怎么能这样做?

3 个答案:

答案 0 :(得分:11)

只需这样:

Expression<Func<int, bool>> exp = i => i < 15 && i > 10;
var compiled = exp.Compile();
exp = i => compiled(i) && i % 2 == 0;  //example additional condition

请注意,你不能这样做:

<击>     exp = i => exp.Compile()(i) && i % 2 == 0; //example additional condition

因为exp将通过引用添加到闭包中,因此调用它将导致StackOverflowException

答案 1 :(得分:4)

您有两种选择。第一个是BartoszKP的版本,黑盒子第一个表达式并在之后使用它。但是,虽然这有很好的语法支持,但它也意味着像Entity Framework这样的系统无法真正使用表达式,因为它是黑盒子的。如果在数据库查询中使用此表达式,则EF无法在服务器上检查此谓词,但必须将所有数据检索到客户端(如果它完全有效)。

因此,如果你想使用表达式,例如对于数据库查询,您必须使用Expression API,即

Expression<Func<int, bool>> exp = i => i<15 && i>10;
exp = Expression.Lambda<Func<int, bool>>(Expression.AndAlso(exp.Body, ...), exp.Parameters[0]);

三个点表示要作为第二部分插入的表达式。您可以使用编译器创建的另一个表达式,但您必须替换参数。

答案 2 :(得分:0)

我从 https://entityframework.net/ 找到了 .Net Framework 6.0 的答案

它也适用于我的 .net 核心

    class Program
    {
        static void Main(string[] args)
        {
            Expression<Func<int, bool>> exprA = a => a == 3;
            Expression<Func<int, bool>> exprB = b => b == 4;
            Expression<Func<int, bool>> exprC =
                Expression.Lambda<Func<int, bool>>(
                    Expression.OrElse(
                        exprA.Body,
                        new ExpressionParameterReplacer(exprB.Parameters, exprA.Parameters).Visit(exprB.Body)),
                    exprA.Parameters);
            Console.WriteLine(exprA.ToString());
            Console.WriteLine(exprB.ToString());
            Console.WriteLine(exprC.ToString());
            Func<int, bool> funcA = exprA.Compile();
            Func<int, bool> funcB = exprB.Compile();
            Func<int, bool> funcC = exprC.Compile();
            Debug.Assert(funcA(3) && !funcA(4) && !funcA(5));
            Debug.Assert(!funcB(3) && funcB(4) && !funcB(5));
            Debug.Assert(funcC(3) && funcC(4) && !funcC(5));
        }
    }

注意:ExpressionParameterReplacer 是一个辅助类,你应该把它放在你的辅助类或任何可访问的地方,并且不存在于标准包中。

public class ExpressionParameterReplacer : ExpressionVisitor
    {
        public ExpressionParameterReplacer(IList<ParameterExpression> fromParameters, IList<ParameterExpression> toParameters)
        {
            ParameterReplacements = new Dictionary<ParameterExpression, ParameterExpression>();
            for (int i = 0; i != fromParameters.Count && i != toParameters.Count; i++)
                ParameterReplacements.Add(fromParameters[i], toParameters[i]);
        }
        private IDictionary<ParameterExpression, ParameterExpression> ParameterReplacements
        {
            get;
            set;
        }
        protected override Expression VisitParameter(ParameterExpression node)
        {
            ParameterExpression replacement;
            if (ParameterReplacements.TryGetValue(node, out replacement))
                node = replacement;
            return base.VisitParameter(node);
        }
    }

我自己场景中的示例

我对此的正常用法是在我的一项服务中添加多个条件,我无法直接访问查询,因此无法使用多个 .Where() 函数:

Expression<Func<Order, bool>> filter = w => ...;

// Extra complex filters which I do not feed to my request models
Expression<Func<Order, bool>> filter2 = null;
switch (model.PredefinedFilter)
{
    case OrderPredefinedFilterEnum.SupportPending:
        filter2 = w => (((w.Cart.CartFlow == CartFlowEnum.Buyer_First_Order_Request &&
                                                  w.Cart.CartStatus == CartStatusEnum.PaidByBuyer)
                                                 || (w.Cart.CartFlow == CartFlowEnum.Seller_First_Suggestion &&
                                                     w.Cart.CartStatus ==
                                                     CartStatusEnum.WaitingForPaymentConfirmByBuyer)) &&
                                                w.Cart.CartSupportStatus == CartSupportStatusEnum.Waiting);
        break;
...
}

if(filter2 != null)
    filter = Expression.Lambda<Func<Order, bool>>(Expression.AndAlso(filter.Body, 
                            new ExpressionParameterReplacer(filter2.Parameters, filter.Parameters).Visit(filter2.Body)),
                        filter.Parameters[0]);

result = (await _orderRepository.GetAllAsNoTrackingAsync(
                    a => totalCount = a,
                    filter,
                    selector,
                    OrderBy,
                    take,
                    skip, cancellationToken: cancellationToken)).ToList();

谢谢:

@Georg 的回答在这件事上帮助了我。谢谢。

另外@BartoszKP asnwer 很酷很简单,但不适用于 EF 和查询,所以我认为它是内存数据的一个很好的解决方案......