如何解析 EF Core 3 查询中的 int?

pba*_*nis 5 entity-framework entity-framework-core ef-core-3.0 ef-core-3.1

升级到 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));
Run Code Online (Sandbox Code Playgroud)

我还尝试使用 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));
Run Code Online (Sandbox Code Playgroud)

然而,我更深入地挖掘,发现这是 EF 从该代码执行的 SQL 查询:

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

显然没有做我想要的事情,因此,克劳迪奥是对的Substring,调用实际上是问题所在。

小智 5

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

事实上,这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
Run Code Online (Sandbox Code Playgroud)

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

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
");
    }
}
Run Code Online (Sandbox Code Playgroud)

然后运行数据库更新:

dotnet ef database update
Run Code Online (Sandbox Code Playgroud)

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
}
Run Code Online (Sandbox Code Playgroud)

3)在查询中使用函数

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

生成的查询:

SELECT MAX(CONVERT(int, [dbo].[fn_Right]([c].[ClaimNumber], 6)))
FROM [Claims] AS [c]
Run Code Online (Sandbox Code Playgroud)

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

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

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