扩展 IQueryable where() 为 OR 而不是 AND 关系

发布于 2024-07-22 07:06:44 字数 693 浏览 8 评论 0 原文

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

但问题是,扩展链中的所有位置(FindXYZ、FindInZip 等)将始终组合为 AND,这意味着我不能执行如下操作:

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

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


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

我想做的事情稍微复杂一些(PredicateBuilder 在这里没有帮助..)因为我希望稍后的 IQueryable 基本上可以访问之前建立的 Where 条件,而不必包装它们以在它们之间创建 Or

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

I am using my own extension methods of IQueryable<> to create chainable queries such as FindAll().FindInZip(12345).NameStartsWith("XYZ").OrderByHowIWantIt() etc. which then on deferred execution creates a single query based on my extension methods chain.

The problem with this though, is that all Where's in the extension chain (FindXYZ, FindInZip etc.) will always combine as AND which means I can't do something like this:

FindAll().FirstNameStartsWith("X").OrLastNameStartsWith("Z") because I don't know how I can inject the OR in a separate Where method.

Any idea how I can solve this?


additional;
So far I understand how to chain expressions as Or if I wrap them (e.g. CompileAsOr(FirstNameStartsWith("A").LastNameStartsWith("Z").OrderBy(..))

What I'm trying to do though is slightly more complicated (and PredicateBuilder doesn't help here..) in that I want a later IQueryable to basically access the Where conditions that were established prior without having to wrap them to create the Or between them.

As each extension method returns IQueryable<> I understand that it should have the knowledge about the current status of query conditions somewhere, which leads me to believe that there should be some automated way or creating an Or across all prior Where conditions without having to wrap what you want Or'd.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

折戟 2024-07-29 07:06:44

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

一个惰性选项是 < code>Concat - 但这往往会导致糟糕的 TSQL 等; 但是,我倾向于编写自定义表达式。 采取的方法取决于提供程序是什么,因为 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);
    }

I'm assuming the different parts of the query are only known at runtime, i.e. you can't just use || in a where...

One lazy option is Concat - but this tends to lead to poor TSQL etc; however, I tend to be inclined to write custom Expressions instead. The approach to take depends on what the provider is, as LINQ-to-SQL supports different options to EF (for example) - which has a genuine impact here (since you can't use sub-expressions with EF). Can you tell us which?


Here's some code that should work with LINQ-to-SQL; if you build an array (or list, and call .ToArray()) of expressions, it should work fine; example is LINQ-to-Objects, but should still work:

    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);
    }
冷清清 2024-07-29 07:06:44

使用 PredicateBuilder。 这可能就是你想要的。

Use PredicateBuilder<T>. It's probably what you want.

格子衫的從容 2024-07-29 07:06:44

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

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

因此我使用对此的扩展方法。 在你的例子中它看起来像这样:
.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/

In an ideal world I personally think || and && operators would be the most simple and readable. However it won't compile.

operator ' ||' cannot be applied to operands of type 'Expression<Func<YourClass,bool>>' and 'Expression<Func<YourClass,bool>>'

Therefore I use an extension method for this. In your example it would look like this:
.Where(FindInZip(12345).Or(NameStartsWith("XYZ")).And(PostedOnOrAfter(DateTime.Now)).

Instead of:

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

Expression example:

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

Extension method:

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
} 

A really good article about LINQ Queries by Extending Expressions. Also the source of the extension method that I use.

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

奈何桥上唱咆哮 2024-07-29 07:06:44
    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);
    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);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文