实体框架,LINQ:不能在 StartsWith 中使用 Any?

Posted

技术标签:

【中文标题】实体框架,LINQ:不能在 StartsWith 中使用 Any?【英文标题】:Entity Framework, LINQ: can't use Any with StartsWith? 【发布时间】:2020-12-23 04:28:55 【问题描述】:

我有一个简单的实体框架 LINQ 查询。目标是找到所有以 A、B 或 C 开头的运营商:

var letters = new List<string>()  "A", "B", "C" ; // Dynamic, can be many
var results = db.Carriers.AsNoTracking()
    .Where(c => letters.Any(val => c.Name.StartsWith(val)))
    .ToList();

我明白了

System.InvalidOperationException: 'LINQ 表达式 'DbSet .Where(c => __letters_0 .Any(val => val == "" || c.Name != null && val != null && c.Name.StartsWith(val)))' 无法翻译。以可翻译的形式重写查询,或通过插入对 AsEnumerable()、AsAsyncEnumerable()、ToList() 或 ToListAsync() 的调用显式切换到客户端评估。请参阅https://go.microsoft.com/fwlink/?linkid=2101038 了解更多信息。'

有没有办法做到这一点?

【问题讨论】:

试试:c.Name.Substring(0, val.Length).Equals(val) 试试.Where(c =&gt; letters.Contains(c.Name[0].ToString())) 应该可以的。 感谢 cmets @iSR5 和 @Alexander Petrov,这两个建议都没有按原样工作,但我能够将它们组合成 letters.Contains(c.Carrier.Name.Substring(0, 1),这似乎有效。问题是它只能使用 1 个字母,并且没有 StartsWith 的灵活性 【参考方案1】:

实体框架不允许使用自定义谓词。 这就是您的查询失败并显示消息的原因:

...无法翻译。要么以可翻译的形式重写查询...

因此,这很可能意味着您的 EF 版本要么不支持 StartsWith,要么存在某种组合,例如无法翻译的 StartsWithAny

解决方法可能是:

    首先,您可以使用Contains 方法以最接近的方式过滤您的查询。
var results = students.Where(s => letters.Any(l => s.name.Contains(l)));

Here 是我为 .NET fiddle 编写的类似示例。


    接下来,如果您仍想使用 StartsWith 但您的 EF 不支持您可以将查询转换为 IN-MEMORY 集合:
var results = filter_by_contains_query
              .ToList() // convert filtered result to in-memory collection
              .Where(i => letters.Any(l => i.StudentName.StartsWith(l)));

filter_by_contains_query 是来自第一部分的查询。


更多:

关于 EF 支持的方法请查看here。 围绕StartsWith,EndsWithContainshere的讨论

【讨论】:

s.name.Contains(...) 示例不会产生我认为的预期结果。 它可能在“执行”的意义上“工作”,但它没有执行所需的操作...... 对不起,这个答案没有多大意义。 1. 什么是“自定义谓词”? 2.ContainsStartsWith不一样。 3. 问题是关于 EF 核心,但您在回答中使用了 EF6。这些版本差别很大。 4. To ToList-trick 不起作用,因为 EF 核心 (3) 也不翻译 Contains 查询。 IMO,您应该删除答案并将代表损失视为理所当然。 @GertArnold, 1) custome_predicate 表示查询 lambda,2) 包含当然与 StartsWith 不同,这就是为什么 3) 如果使用 NOT EF 的人核心版本,但不包含 StartsWith 的第 6 版或其他版本可能会使用 4) ToList() 来将查询转换为内存中的集合,该集合已被 Contains 过滤并使用StartsWith over。 "这很可能意味着您的 EF 版本要么不支持 StartsWith,要么存在某种组合,例如 StartsWith 和 Any 无法翻译" Any+StartsWith 正在对输入参数 (letters) 执行。 Any+StartsWith 在查询数据库表时起作用,因为 SQL 确实支持它,但 SQL 不支持对输入参数的查询逻辑。【参考方案2】:

在 Entity Framework 6 中,此查询可以正常运行。 EF6 支持StartsWith 的SQL 翻译,并且支持在查询表达式中使用本地序列(letters)。

EF core 3(问题中的版本如异常消息所示)还支持StartsWith 的SQL 翻译。这里的问题(另一个答案完全错过了)是本地序列的使用方式不受支持。类似的查询...

var results = db.Carriers.AsNoTracking()
    .Where(c => letters.Contains(c.Name))
    .ToList();

...将被支持,因为 letters 可以简单地转换为 IN 子句。但当然这是一个完全不同的查询。

使用letters.Any 需要EF 将letters 转换为可以在SQL 中连接的“某物”。 EF6 通过在 SQL 查询中构建结果集来做到这一点:

    WHERE  EXISTS (SELECT 
        1 AS [C1]
        FROM  (SELECT 
            N'A' AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]
        UNION ALL
            SELECT 
            N'B' AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable2]
        UNION ALL
            SELECT 
            N'C' AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable3]) AS [UnionAll2]
        WHERE ( CAST(CHARINDEX([UnionAll2].[C1], [Extent1].[Name]) AS int)) = 1

这可行,但根本不可扩展。 EF core 3 不支持它,并且没有其他答案中建议的简单解决方法。

一种可能的解决方法是使用||(或)谓词构建谓词,例如:

var pred = letters.Aggregate(PredicateBuilder.False<Carrier>(), 
    (p,x) => p = p.Or(c => c.Name.StartsWith(x)));
var results = db.Carriers.AsNoTracking()
    .Where(pred)
    .ToList();

PredicateBuilder 是一个谓词构建器,如 Linqkit 或 this one。但是这种方法也不是可扩展的。 EF 为letters 中的每个条目创建一个参数,因此您可能会在 Sql Server 中达到 2100 个参数的阈值。

【讨论】:

以上是关于实体框架,LINQ:不能在 StartsWith 中使用 Any?的主要内容,如果未能解决你的问题,请参考以下文章

像实体框架中的运算符?

使用 SQL Server Compact 4.0 在实体框架中进行 LIKE 查询

实体框架/LINQ/SQL 与实体框架/LINQ/MYSQL

使用 SQL 和 Linq 的多对多关系(实体框架/实体)

实体框架 Linq 查询:.Where 链 vs &&

动态 SQL 到 LINQ 实体框架