实体框架,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 => letters.Contains(c.Name[0].ToString()))
应该可以的。
感谢 cmets @iSR5 和 @Alexander Petrov,这两个建议都没有按原样工作,但我能够将它们组合成 letters.Contains(c.Carrier.Name.Substring(0, 1)
,这似乎有效。问题是它只能使用 1 个字母,并且没有 StartsWith 的灵活性
【参考方案1】:
实体框架不允许使用自定义谓词。 这就是您的查询失败并显示消息的原因:
...无法翻译。要么以可翻译的形式重写查询...
因此,这很可能意味着您的 EF 版本要么不支持 StartsWith
,要么存在某种组合,例如无法翻译的 StartsWith
和 Any
。
解决方法可能是:
-
首先,您可以使用
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
,EndsWith
和Contains
here的讨论
【讨论】:
s.name.Contains(...)
示例不会产生我认为的预期结果。
它可能在“执行”的意义上“工作”,但它没有执行所需的操作......
对不起,这个答案没有多大意义。 1. 什么是“自定义谓词”? 2.Contains
与StartsWith
不一样。 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 查询