【问题标题】:EF Core - Unable to query if entity.Name exists in List<string>EF Core - 无法查询 list<string> 中是否存在 entity.Name
【发布时间】:2020-12-15 08:42:46
【问题描述】:

使用Entity Framework Core 3.1.7

我在数据库中有一个包含 Products 的表。

public class Product 
{
   public int Id {get; set;}
   public string Name {get; set;}
}

然后我希望用户能够使用搜索字段在 UI 表中查找某些产品。 当查询进来时,我尝试执行以下操作:

var searchParameters = query.SearchParameters.ToLower().Split(' ', ',', '+').Distinct();

var result = _context.Products
                    .Where(p => searchParameters.Any()
                    && (searchParameters.Any(x => p.Name.ToLower().Contains(x)) //Version 1
                    ).ToList();

或替代

searchParameters.Any(x => EF.Functions.Like(p.Name, "%" + x + "%")) //Version 2

但是,我对这个看似简单的事情进行了调整:

LINQ 表达式 'DbSet .Where(p => __searchParameters_0 .Any(x => p.Name.ToLower().Contains(x)))' 无法翻译。要么以可翻译的形式重写查询, 或通过插入调用显式切换到客户端评估 AsEnumerable()、AsAsyncEnumerable()、ToList() 或 ToListAsync()

我意识到.ToLower() 将是一个问题,所以我想为 SQL 查询运行一个不区分大小写的搜索的 LIKE 语句。但即便如此,List&lt;string&gt; 也没有被翻译。

【问题讨论】:

  • 请注意,根据您的 SQL Server 设置,不区分大小写的匹配很可能是默认设置,因此您无需对搜索进行不区分大小写的翻译。
  • @NetMage 是的,谢谢!我尝试忽略 ToLower,它能够找到结果,但前提是我每个搜索参数触发一个查询,因为 .Any 不翻译。

标签: sql-server .net-core linq-to-sql entity-framework-core


【解决方案1】:

如果您愿意使用LINQKit(或模拟谓词构建器部分),您可以使用扩展方法将Any(...Contains) 表达式扩展为“或”表达式:

public static class LinqKitExt { // using LINQKit
    // keyFne - extract string key from row
    // searchTerms - IEnumerable<string> where one must be contained by a row's key
    // dbq.Where(r => searchTerms.Any(s => keyFne(r).Contains(s)))
    public static IQueryable<T> WhereContainsAny<T>(this IQueryable<T> dbq, Expression<Func<T,string>> keyFne, IEnumerable<string> searchTerms) {
        var pred = PredicateBuilder.New<T>();
        foreach (var s in searchTerms)
            pred = pred.Or(r => keyFne.Invoke(r).Contains(s));

        return dbq.Where((Expression<Func<T,bool>>)pred.Expand());
    }
}

(以及 Where/OrderBy[Descending] Any/All Contains/StartsWith 的 51 种其他变体。)

那你就可以这样使用了

var result = _context.Products
                     .WhereContainsAny(r => r.Name, searchParameters)
                     .ToList();

PS 追求另一个答案,我意识到将测试拉给调用者消除了大多数变化:

// searchTerms - IEnumerable<TKey> where all must be in a row's key
// testFne(row,searchTerm) - test one of searchTerms against a row
// dbq.Where(r => searchTerms.Any(s => testFne(r,s)))
public static IQueryable<T> WhereAny<T,TKey>(this IQueryable<T> dbq, IEnumerable<TKey> searchTerms, Expression<Func<T, TKey, bool>> testFne) {
    var pred = PredicateBuilder.New<T>();
    foreach (var s in searchTerms)
        pred = pred.Or(r => testFne.Invoke(r, s));

    return dbq.Where((Expression<Func<T,bool>>)pred.Expand());
}

然后你只需调用:

var result = _context.Products
                     .WhereAny(searchParameters, (r,s) => r.Name.Contains(s))
                     .ToList();

【讨论】:

  • 工作如梦!谢谢!我只是好奇,之前不是为实体框架(不是核心)启用了这种行为吗?无论哪种方式,我现在都坚持使用谓词构建器!非常感谢!
  • @RKrogh 我相信你可能是对的,EF Core(甚至 5 个)已经从 EF 6 中丢失了很多翻译,以追求更加一致(并且通常不太有用)......跨度>
  • @RKrogh 仅供参考,我想出了一种方法,通过将测试拉给调用者来使其更通用;并修复了原始答案中ExpressionStarter 处理中的错误
  • 不错!谢谢你回到我身边。我喜欢第二个版本。会检查出来。什么是错误?自从你给它以来我一直在运行它,但从未注意到任何东西:)
  • @RKrogh 通过不使用var 和pred,第一次调用.Or 或.And 并没有意识到它需要删除启动器,而是默认为false与.Or 一起工作,但与.And 一起失败。 (基本上编译器会调用错误版本的.Or 或.And 方法。)
猜你喜欢
  • 2017-03-19
  • 2021-02-20
  • 1970-01-01
  • 1970-01-01
  • 2021-10-06
  • 1970-01-01
  • 2021-02-22
  • 2013-05-15
  • 1970-01-01
相关资源
最近更新 更多