9

我正在使用自己的 IQueryable<> 扩展方法来创建可链接的查询,例如 FindAll().FindInZip(12345).NameStartsWith("XYZ").OrderByHowIWantIt() 等,然后在延迟执行时根据我的扩展方法链。

但问题在于,扩展链中的所有 Where (FindXYZ、FindInZip 等)将始终组合为 AND,这意味着我不能做这样的事情:

FindAll().FirstNameStartsWith("X").OrLastNameStartsWith("Z") 因为我不知道如何在单独的 Where 方法中注入 OR。

知道如何解决这个问题吗?


额外的; 到目前为止,我了解如何将表达式链接为 Or 如果我包装它们(例如 CompileAsOr(FirstNameStartsWith("A").LastNameStartsWith("Z").OrderBy(..)))

我正在尝试做的事情稍微复杂一些(并且 PredicateBuilder 在这里没有帮助..)因为我希望稍后的 IQueryable 基本上访问之前建立的 Where 条件,而不必包装它们来创建 Or between他们。

由于每个扩展方法都返回 IQueryable<> 我知道它应该了解某处查询条件的当前状态,这使我相信应该有一些自动化的方式或在所有先前的 Where 条件中创建一个 Or 而不必包装你想要什么或'd。

4

4 回答 4

19

我假设查询的不同部分仅在运行时才知道,即您不能只||where...

一个懒惰的选择是Concat- 但这往往会导致糟糕的 TSQL 等;但是,我倾向于改为编写 custom Expression。采取的方法取决于提供者是什么,因为 LINQ-to-SQL 支持 EF 的不同选项(例如) - 这在这里有真正的影响(因为您不能将子表达式与 EF 一起使用)。你能告诉我们是哪个吗?


下面是一些适用于 LINQ-to-SQL 的代码;如果您构建一个表达式数组(或列表,并调用.ToArray()),它应该可以正常工作;示例是 LINQ-to-Objects,但仍应有效:

    static void Main()
    {
        var data = (new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }).AsQueryable();

        var predicates = new List<Expression<Func<int, bool>>>();
        predicates.Add(i => i % 3 == 0);
        predicates.Add(i => i >= 8);           

        foreach (var item in data.WhereAny(predicates.ToArray()))
        {
            Console.WriteLine(item);
        }
    }

    public static IQueryable<T> WhereAny<T>(
        this IQueryable<T> source,
        params Expression<Func<T,bool>>[] predicates)
    {
        if (source == null) throw new ArgumentNullException("source");
        if (predicates == null) throw new ArgumentNullException("predicates");
        if (predicates.Length == 0) return source.Where(x => false); // no matches!
        if (predicates.Length == 1) return source.Where(predicates[0]); // simple

        var param = Expression.Parameter(typeof(T), "x");
        Expression body = Expression.Invoke(predicates[0], param);
        for (int i = 1; i < predicates.Length; i++)
        {
            body = Expression.OrElse(body, Expression.Invoke(predicates[i], param));
        }
        var lambda = Expression.Lambda<Func<T, bool>>(body, param);
        return source.Where(lambda);
    }
于 2009-05-30T21:50:12.150 回答
9

使用PredicateBuilder<T>. 这可能是你想要的。

于 2009-05-30T21:45:46.557 回答
0
    List<string> fruits =
        new List<string> { "apple", "passionfruit", "banana", "mango",
               "orange", "blueberry", "grape", "strawberry" };

    var query = fruits.AsQueryable();

    // Get all strings whose length is less than 6.
    query = query.Where(fruit => fruit.Length < 6);

    // Hope to get others where length is more than 8.  But you can't, they're gone.
    query = query.Where(fruit => 1 == 1 || fruit.Length > 8);

    foreach (string fruit in query)
        Console.WriteLine(fruit);
于 2016-09-14T23:36:57.513 回答
0

在一个理想的世界里,我个人认为||&&操作符将是最简单和可读的。但是它不会编译。

运算符“||” 不能应用于“ Expression<Func<YourClass,bool>>”和“ Expression<Func<YourClass,bool>>”类型的操作数

因此,我为此使用了扩展方法。在您的示例中,它看起来像这样 .Where(FindInZip(12345).Or(NameStartsWith("XYZ")).And(PostedOnOrAfter(DateTime.Now))

代替:

.Where(FindInZip(12345) || NameStartsWith("XYZ") && (PostedOnOrAfter(DateTime.Now)).

表达式示例:

private Expression<Func<Post,bool>> PostedOnOrAfter(DateTime cutoffDate)
{
      return post => post.PostedOn >= cutoffDate;
};

扩展方法:

public  static  class PredicateExtensions
{
     ///  <summary>
     /// Begin an expression chain
     ///  </summary>
     ///  <typeparam id="T""></typeparam>
     ///  <param id="value"">Default return value if the chanin is ended early</param>
     ///  <returns>A lambda expression stub</returns>
     public  static Expression<Func<T,  bool>> Begin<T>(bool value =  false)
    {
         if (value)
             return parameter =>  true;  //value cannot be used in place of true/false

         return parameter =>  false;
    }

     public  static Expression<Func<T,  bool>> And<T>(this Expression<Func<T,  bool>> left,
        Expression<Func<T,  bool>> right)
    {
         return CombineLambdas(left, right, ExpressionType.AndAlso);
    }

     public  static Expression<Func<T,  bool>> Or<T>(this Expression<Func<T,  bool>> left, Expression<Func<T,  bool>> right)
    {
         return CombineLambdas(left, right, ExpressionType.OrElse);
    }

     #region private

     private  static Expression<Func<T,  bool>> CombineLambdas<T>(this Expression<Func<T,  bool>> left,
        Expression<Func<T,  bool>> right, ExpressionType expressionType)
    {
         //Remove expressions created with Begin<T>()
         if (IsExpressionBodyConstant(left))
             return (right);

        ParameterExpression p = left.Parameters[0];

        SubstituteParameterVisitor visitor =  new SubstituteParameterVisitor();
        visitor.Sub[right.Parameters[0]] = p;

        Expression body = Expression.MakeBinary(expressionType, left.Body, visitor.Visit(right.Body));
         return Expression.Lambda<Func<T,  bool>>(body, p);
    }

     private  static  bool IsExpressionBodyConstant<T>(Expression<Func<T,  bool>> left)
    {
         return left.Body.NodeType == ExpressionType.Constant;
    }

     internal  class SubstituteParameterVisitor : ExpressionVisitor
    {
         public Dictionary<Expression, Expression> Sub =  new Dictionary<Expression, Expression>();

         protected  override Expression VisitParameter(ParameterExpression node)
        {
            Expression newValue;
             if (Sub.TryGetValue(node,  out newValue))
            {
                 return newValue;
            }
             return node;
        }
    }

     #endregion
} 

一篇关于通过扩展表达式进行 LINQ 查询的非常好的文章。也是我使用的扩展方法的来源。

https://www.red-gate.com/simple-talk/dotnet/net-framework/giving-clarity-to-linq-queries-by-extending-expressions/

于 2018-09-12T10:58:11.903 回答