【问题标题】:How to simplify repetitive OR condition in Where(e => e.prop1.contains() || e.prop2.contains() || ...)如何简化 Where(e => e.prop1.contains() || e.prop2.contains() || ...) 中的重复 OR 条件
【发布时间】:2021-03-07 12:24:27
【问题描述】:

我有很多这样的代码:

IQueryable<MyEntity> query...
query.Where(e => EF.Functions.Like(e.Property1,  pattern)
                || EF.Functions.Like(e.Property2, pattern)
                || EF.Functions.Like(e.Property3, pattern)
                ...);

如何简化它以将重复代码删除为这样的:

query.Where(Search(pattern, e => e.Property1, e => e.Property2, e => e.Property3));  

query.Where(e => Search(pattern, e.Property1,  e.Property2,  e.Property3))  

编辑:查询必须由 EF 转换为 SQL,因此 where 条件不能只是任何返回布尔值的函数。必须是可翻译的 Expression>

【问题讨论】:

    标签: c# linq lambda entity-framework-core


    【解决方案1】:

    对于顶级IQueryable&lt;T&gt;(即不是查询表达式树的一部分),这可以通过自定义扩展方法接收匹配值、谓词表达式和表达式列表以及构建or 谓词来实现从他们。像这样的:

    public static partial class QueryableExtensions
    {
        public static IQueryable<T> WhereAnyMatch<T, V>(this IQueryable<T> source,
            V value, Expression<Func<V, V, bool>> match,
            params Expression<Func<T, V>>[] itemsToMatch)
        {
            var parameter = Expression.Parameter(typeof(T), "e");
            Expression<Func<V>> arg = () => value; // to let EF Core bind a parameter rather tnan a constant
            var body = itemsToMatch
                .Select(item => Expression.Invoke(match, Expression.Invoke(item, parameter), arg.Body))
                .Aggregate<Expression>(Expression.OrElse);
            var predicate = Expression.Lambda<Func<T, bool>>(body, parameter);
            return source.Where(predicate);
        }
    }
    

    您的案例的示例用法是:

    .WhereAnyMatch(pattern, (item, arg) => EF.Functions.Like(item, arg),
        e => e.Property1, e => e.Property2, e => e.Property3);
    

    当然,一旦你有了那个方法,你可以添加其他的常用匹配谓词,例如Like

    public static partial class QueryableExtensions
    {
        public static IQueryable<T> WhereAnyLike<T>(this IQueryable<T> source,
            string pattern, params Expression<Func<T, string>>[] itemsToMatch)
            => source.WhereAnyMatch(pattern, (item, arg) => EF.Functions.Like(item, arg), itemsToMatch);
    }
    

    示例代码将简化为

    .WhereAnyLike(pattern, e => e.Property1, e => e.Property2, e => e.Property3);
    

    【讨论】:

      【解决方案2】:

      您可以重写它以将所有属性组合到一个数组中并检查是否匹配:

      query.Where(e =>
          new[]{ e.Property1, e.Property2, e.Property3}
            .Any(p => EF.Functions.Like(p, pattern) )
      );
      

      等效的 SQL 是:

      WHERE EXISTS (SELECT 1
          FROM (VALUES
            (e.Property1),
            (e.Property2),
            (e.Property3)
          ) v(Prop)
          WHERE v.Prop LIKE @pattern
      )
      

      等价的&amp;&amp;/AND语义略有不同:

      query.Where(e =>
          new[]{ e.Property1, e.Property2, e.Property3}
            .All(p => EF.Functions.Like(p, pattern) )
      );
      

      等价的SQL是双重否定的:

      WHERE NOT EXISTS (SELECT 1
          FROM (VALUES
            (e.Property1),
            (e.Property2),
            (e.Property3)
          ) v(Prop)
          WHERE v.Prop NOT LIKE @pattern
      )
      

      【讨论】:

      • 这真的会被翻译成sql吗?
      • @TimSchmelter 如果是实体框架,是的,为什么不呢?我把它放在那里更多是为了让人们了解它是如何工作的以及如何在原始 SQL 中完成它
      • 这样的sql查询性能如何?抱歉,我从来没有真正理解那些查询执行计划,真丢脸:O
      • @Charlieface:我不怎么使用 EF,所以我不知道,我只是很惊讶inMemoryCollection.Any(...) 将转换为单个纯 sql 查询。
      • 应该没问题,它只会循环遍历每一行的每个字段。如果您正在执行StartsWithLIKE 'abc%',那么最好不要在索引列上执行此操作
      【解决方案3】:

      在 60 年代后期发明了一种避免重复输入类似代码的方法。他们称之为程序。

      那么为什么不用WhereLike 方法扩展 IQueryable 呢?完成此操作后,您可以将它与其他 LINQ 语句交织在一起,就好像它是一种正确的 LINQ 方法一样。

      如果您不熟悉扩展方法,请阅读extension methods demystified

      我们可以为两个或三个 Like 语句或任何有限数创建扩展方法。

      public static IQueryable<T> WhereLike<T>(this IQueryable<T> source,
          Func<T, string> propertySelector1,
          Func<T, string> propertySelector2,
          string pattern)
      {
          return source.Where(item => EF.Functions.Like(propertySelector1(item, pattern)
                                   || EF.Functions.Like(propertySelector2(item, pattern));
      }
      

      (类似 3 个属性选择器)

      用法:

      获取名字或姓氏类似于“John%”的客户

      var result = dbContext.Customers.
          .WhereLike(customer => customer.FirstName,
                     customer => customer.LastName,
                     "John%");
      

      【讨论】:

        猜你喜欢
        • 2017-11-10
        • 2017-11-28
        • 2012-11-10
        • 1970-01-01
        • 1970-01-01
        • 2023-03-10
        • 2016-05-18
        • 1970-01-01
        • 2020-01-28
        相关资源
        最近更新 更多