首页 > 解决方案 > 如何在 EF Core 3 查询中解析 int?

问题描述

升级到 EF Core 3 后,我在以下代码中收到以下错误:

System.InvalidOperationException:'无法翻译 LINQ 表达式'DbSet .Max(c => Convert.ToInt32(c.ClaimNumber.Substring(c.ClaimNumber.Length - 6)))'。以可翻译的形式重写查询,或通过插入对 AsEnumerable()、AsAsyncEnumerable()、ToList() 或 ToListAsync() 的调用显式切换到客户端评估。有关详细信息,请参阅https://go.microsoft.com/fwlink/?linkid=2101038

var maxId = Db.Claims
    .Select(c => c.ClaimNumber.Substring(c.ClaimNumber.Length - 6))
    .Max(x => Convert.ToInt32(x));

我也尝试过使用 int.Parse 而不是 Convert.ToInt32,它会产生相同的错误。我理解错误信息。但是,让 SQL Server 使用 CAST 或 CONVERT 将字符串解析为 T-SQL 中的 int 是微不足道的,我希望有一种简单的方法来编写查询,以便将其转换为服务器端操作,对吗?

更新克劳迪奥的出色回答之后,我想我应该为下一个出现的人添加一些信息。我认为解析是上面代码的问题的原因是因为以下运行没有错误并产生正确的结果:

var maxId = Db.Claims
    .Select(c => c.ClaimNumber.Substring(c.ClaimNumber.Length - 6))
    .AsEnumerable()
    .Max(x => int.Parse(x));

但是,我深入挖掘并发现这是 EF 正在从该代码执行的 SQL 查询:

SELECT [c].[ClaimNumber], CAST(LEN([c].[ClaimNumber]) AS int) - 6
FROM [Claims] AS [c]
WHERE [c].[ClaimNumber] IS NOT NULL

显然不像我想要的那样做任何事情,因此,克劳迪奥是正确的,调用Substring实际上是问题所在。

标签: entity-frameworkentity-framework-coreef-core-3.0ef-core-3.1

解决方案


免责声明:虽然可行,但我强烈建议您不要在查询中使用类型转换,因为这会导致查询性能严重下降。

事实上,这Convert.ToInt(x)部分不是问题所在。也就是说 c.ClaimsNumber.Substring(c.ClaimNumber.Length - 6),EF Core 翻译器无法在 T-SQL 中翻译。

尽管RIGHTSql Server 中存在函数,但您也无法将它与当前版本的 EF Core 一起使用(在我撰写本文时,最新版本是 3.1.2)。获得所需内容的唯一解决方案是创建一个 Sql Server 用户函数,将其与 EF Core 映射并在查询中使用它。

1) 通过迁移创建函数

> dotnet ef migrations add CreateRightFunction

在新创建的迁移文件中放入以下代码:

public partial class CreateRightFunctions : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.Sql(@"
CREATE FUNCTION fn_Right(@input nvarchar(4000), @howMany int)
RETURNS nvarchar(4000)
BEGIN
RETURN RIGHT(@input, @howMany)
END
");
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.Sql(@"
DROP FUNCTION fn_Right
");
    }
}

然后运行数据库更新:

dotnet ef database update

2) 将函数映射到 EF Core 上下文

在你的上下文类[DbFunction("fn_Right")]

public static string Right(string input, int howMany)
{
    throw new NotImplementedException(); // this code doesn't get executed; the call is passed through to the database function
}

3) 在查询中使用函数

var maxId = Db.Claims.Select(c => MyContext.Right(c.ClaimNumber, 6)).Max(x => Convert.ToInt32(x));

生成的查询:

SELECT MAX(CONVERT(int, [dbo].[fn_Right]([c].[ClaimNumber], 6)))
FROM [Claims] AS [c]

同样,这远非最佳实践,我认为您应该考虑在表中添加一个 int 列来存储此“数字”,无论它在您的域中代表什么。

此外,ClaimNumber 的最后 6 个字符第一次包含非数字字符,这将不再起作用。如果 ClaimNumber 是由人输入的,迟早会发生这种情况。

即使您非常确定这 6 个字符将始终代表一个数字,您也应该编码和设计您的数据库和应用程序以实现稳健性。他们不能永远这样做:)


推荐阅读