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

How to Parse an int in an EF Core 3 Query?

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

System.InvalidOperationException: 'The LINQ expression 'DbSet .Max(c => Convert.ToInt32(c.ClaimNumber.Substring(c.ClaimNumber.Length - 6)))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.'

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

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

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

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

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

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

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

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

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

中翻译

尽管 RIGHT 函数存在于 Sql 服务器中,但您也无法将其用于当前版本的 EF Core(目前最新版本是 3.1.2写作)。 获得所需内容的唯一解决方案是创建一个 Sql 服务器用户函数,将其映射到 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 上下文

在您的上下文中 class[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]

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

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

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

请按以下方式更改您的代码。它在 Dotnet 核心 3.1 版本中为我工作

var maxId = Db.Claims.Select(c => c.ClaimNumber.Substring(c.ClaimNumber.Length - 6))
    .Max(x => (Convert.ToInt32((x == null)? "0" : x.ToString())));