【问题标题】:C# multiple OR conditions in LINQ query [duplicate]LINQ查询中的C#多个OR条件[重复]
【发布时间】:2021-08-07 09:47:25
【问题描述】:

我正在尝试使用 LINQ 链接多个值或循环。

情况

平台:.net 5C# 9

我们正在为列表构建过滤逻辑。在当前情况下,它涉及要过滤的字符串值。
用户可以搜索一个或多个值。他可以决定单个搜索词是否为 AND/OR 链接,以及某个值是否为否定。

我看到了这个条目。但由于我的值在循环中,我不能使用||
https://stackoverflow.com/a/37195788/1847143

例子:

  • 名称中带有“A”的所有动物
    SELECT * FROM "Animal" WHERE "Name" = 'A';
  • 名称中带有“A”或“B”的所有动物
    SELECT * FROM "Animal" WHERE "Name" = 'A' OR "Name" = 'B';
  • 名称中包含“A”或“B”或非“C”的所有动物(这将是毫无意义的搜索)
    SELECT * FROM "Animal" WHERE "Name" = 'A' OR "Name" = 'B' OR "Name" != 'C' ;
  • 名称中带有“A”和“B”的所有动物
    SELECT * FROM "Animal" WHERE "Name" = 'A' AND "Name" = 'B';
  • 名称中包含“A”和“B”而不是“C”的所有动物
    SELECT * FROM "Animal" WHERE "Name" = 'A' AND "Name" = 'B' AND "Name" != 'C';

问题

与 LINQ 的 AND 链接没有问题。但是这些值如何与 OR 联系起来呢?

代码示例

using System.Collections.Generic;
using System.Linq;

namespace SampleProject
{
    public class Program
    {
        public static void Main(string[] args)
        {
            // Or Condtion
            Condition condition = Condition.Or;

            var animalsQuery = Animals.AsQueryable();

            // Loop over all search values to extend the query
            foreach (FilterValue filterValue in FilterValues)
            {
                switch (filterValue.LikeType)
                {
                    case LikeType.Left: // LIKE '%value'
                        animalsQuery = filterValue.IsNegated
                            ? animalsQuery.Where(animal => !animal.Name.EndsWith(filterValue.Value))
                            : animalsQuery.Where(animal => animal.Name.EndsWith(filterValue.Value));

                        break;

                    case LikeType.Right: // LIKE 'value%'
                        animalsQuery = filterValue.IsNegated
                            ? animalsQuery.Where(animal => !animal.Name.StartsWith(filterValue.Value))
                            : animalsQuery.Where(animal => animal.Name.StartsWith(filterValue.Value));

                        break;

                    case LikeType.LeftAndRight: // LIKE '%value%'
                        animalsQuery = filterValue.IsNegated
                            ? animalsQuery.Where(animal => !animal.Name.Contains(filterValue.Value))
                            : animalsQuery.Where(animal => animal.Name.Contains(filterValue.Value));

                        break;

                    case LikeType.Equals: // Like 'value'
                        animalsQuery = filterValue.IsNegated
                            ? animalsQuery.Where(animal => animal.Name != filterValue.Value)
                            : animalsQuery.Where(animal => animal.Name == filterValue.Value);
                        break;
                }
            }

            var result = animalsQuery.ToList();
        }

        /// Values to filter
        public static List<Animal> Animals = new()
        {
            new() {Name = "Lenny"},
            new() {Name = "Gideon"},
            new() {Name = "Shania"},
            new() {Name = "Jada"},
            new() {Name = "Kamil"},
            new() {Name = "Fariha"},
        };

        /// Search Values
        public static List<FilterValue> FilterValues = new()
        {
            new() {Value = "a", LikeType = LikeType.Left},
            new() {Value = "n", LikeType = LikeType.Right},
            new() {Value = "f", LikeType = LikeType.LeftAndRight},
            new() {Value = "k", LikeType = LikeType.Equals},
        };
    }

    public class Animal
    {
        public string Name { get; set; }
    }

    public class FilterValue
    {
        public string   Value     { get; set; }
        public bool     IsNegated { get; set; }
        public LikeType LikeType  { get; set; }
    }

    public enum LikeType
    {
        Left         = 1,
        Right        = 2,
        LeftAndRight = 3,
        Equals       = 4,
    }

    public enum Condition
    {
        And = 1,
        Or  = 2,
    }
}

【问题讨论】:

  • 如果OR 都等于而不是不等于,那么问题很简单——使用Contains(即有一个List&lt;string&gt; 的值)。 != 唉,这不会那么好。
  • 与其链接多个 Where 调用,不如为每个条件创建一个 lambda,将这些 lambda 与 &amp;&amp; 和/或 || 运算符组合,然后使用组合结果进行一个 Where 调用.
  • 你可能需要一个 PredicateBuilder;即可以创建和组合 linq 查询谓词的类。它通常包括 OR、AND、NOT、XOR、TRUE 和 FALSE 的方法。它的输入是针对项目类的属性名称或 lambda getter 表达式(在您的示例中为“Animal”)。
  • @mjwills 但是这样我不能将StartsWithEndsWith 结合起来。所有“以 A 开头”和“以 E 结尾”的动物。 SELECT * FROM "Animal" WHERE "Name" LIKE 'A%' AND "Name" LIKE '%E';
  • @TheBigNeo 没错。

标签: c# linq .net-5 sql-to-linq-conversion


【解决方案1】:

这进入了表达式树重写的领域。好消息是:它不是非常复杂并且你可以同时执行你的否定步骤:

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;

namespace SampleProject
{
    public class Program
    {
        static Expression<Func<T, bool>> Combine<T>(Condition condition, Expression<Func<T, bool>> left, Expression<Func<T, bool>> right, bool negateRight)
        {
            if (right is null) return left;
            if (left is null)
            {
                return negateRight ?
                    Expression.Lambda<Func<T, bool>>(
                         Expression.Not(right.Body), right.Parameters)
                    : right;
            }

            var leftP = left.Parameters.Single();
            var rightP = right.Parameters.Single();

            var rightBody = right.Body;
            if (!ReferenceEquals(leftP, rightP))
            {
                // swap all uses of rightP on rightBody to leftP
                // i.e. normalize on the parameter
                rightBody = new SwapVisitor(rightP, leftP).Visit(rightBody);
            }
            if (negateRight)
            {
                rightBody = Expression.Not(rightBody);
            }
            return Expression.Lambda<Func<T, bool>>(condition switch
            {
                Condition.And => Expression.AndAlso(left.Body, rightBody),
                Condition.Or => Expression.OrElse(left.Body, rightBody),
                _ => throw new ArgumentOutOfRangeException(nameof(condition)),
            }, left.Parameters);
        }

        class SwapVisitor : ExpressionVisitor
        {
            private readonly Expression _from, _to;
            public SwapVisitor(Expression from, Expression to)
            {
                _from = from;
                _to = to;
            }
            public override Expression Visit(Expression node)
                => ReferenceEquals(node, _from) ? _to : base.Visit(node);
        }
        public static void Main(string[] args)
        {
            // Or Condtion
            Condition condition = Condition.Or;

            var animalsQuery = Animals.AsQueryable();
            // Loop over all search values to extend the query
            Expression<Func<Animal, bool>> predicate = null;
            foreach (FilterValue filterValue in FilterValues)
            {
                switch (filterValue.LikeType)
                {
                    case LikeType.Left: // LIKE '%value'
                        predicate = Combine(condition, predicate, animal => animal.Name.EndsWith(filterValue.Value), filterValue.IsNegated);

                        break;

                    case LikeType.Right: // LIKE 'value%'
                        predicate = Combine(condition, predicate, animal => animal.Name.StartsWith(filterValue.Value), filterValue.IsNegated);

                        break;

                    case LikeType.LeftAndRight: // LIKE '%value%'
                        predicate = Combine(condition, predicate, animal => animal.Name.Contains(filterValue.Value), filterValue.IsNegated);

                        break;

                    case LikeType.Equals: // Like 'value'
                        predicate = Combine(condition, predicate, animal => animal.Name == filterValue.Value, filterValue.IsNegated);
                        break;
                }
            }

            if (predicate is not null)
            {
                animalsQuery = animalsQuery.Where(predicate);
            }
            var result = animalsQuery.ToList();
        }

        /// Values to filter
        public static List<Animal> Animals = new()
        {
            new() { Name = "Lenny" },
            new() { Name = "Gideon" },
            new() { Name = "Shania" },
            new() { Name = "Jada" },
            new() { Name = "Kamil" },
            new() { Name = "Fariha" },
        };

        /// Search Values
        public static List<FilterValue> FilterValues = new()
        {
            new() { Value = "a", LikeType = LikeType.Left },
            new() { Value = "n", LikeType = LikeType.Right },
            new() { Value = "f", LikeType = LikeType.LeftAndRight },
            new() { Value = "k", LikeType = LikeType.Equals },
        };
    }

    public class Animal
    {
        public string Name { get; set; }
    }

    public class FilterValue
    {
        public string Value { get; set; }
        public bool IsNegated { get; set; }
        public LikeType LikeType { get; set; }
    }

    public enum LikeType
    {
        Left = 1,
        Right = 2,
        LeftAndRight = 3,
        Equals = 4,
    }

    public enum Condition
    {
        And = 1,
        Or = 2,
    }
}

【讨论】:

  • 非常感谢。这完美地工作。 Combine开头有个小bug。如果第一个/单个right 值是left == nullright != nullnegateRight == true,那么right 不会被否定。 if (left is null) return negateRight ? Expression.Lambda&lt;Func&lt;T, bool&gt;&gt;(Expression.Not(right.Body), right.Parameters) : right;
  • @TheBigNeo 够公平的!
猜你喜欢
  • 2012-12-03
  • 1970-01-01
  • 2017-01-27
  • 1970-01-01
  • 1970-01-01
  • 2021-12-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多