使用 EF Core 3.1 为 SQL 服务器序列获取 "NEXT VALUE FOR" - 不可能?

Getting "NEXT VALUE FOR" for a SQL Server sequence using EF Core 3.1 - impossible?

我正在编写一个新的 ASP.NET Core Web API,我的一个要求是能够利用 EF Core 3.1 获取 的下一个值sequence 在我的 SQL 服务器中定义为我需要存储的记录的 ID。

我正在努力寻找一种方法来执行此操作 - 在 EF 6.x 中,我直接在 DbContext 后代上使用了一种方法,如下所示:

public int GetNextSequenceValue()
{
    var rawQuery = Database.SqlQuery<int>("SELECT NEXT VALUE FOR dbo.TestSequence;");
    var task = rawQuery.SingleAsync();
    int nextVal = task.Result;

    return nextVal;
}

对于 EF Core 2.1,我本来可以使用 Database.ExecuteSqlCommand() 到 运行 一个 SQL 片段并得到结果。但似乎,在 EF Core 3.x 中,我运气不好....

我知道 DbSet 上有 .FromSqlRaw().FromSqlInterpolated 方法 - 但因为我只需要 return 序列的下一个值(INT), 那是不会飞的。而且我也知道这些方法也存在于 context.Database 级别,看起来非常接近我在 EF 6.x 中的方法 - 但在这里,这些方法将 return 受影响的行数 - 我还没有找到从 SEQUENCE.

发回新值的方法

难道真的是在 EF Core 3.x 中,我实际上不得不求助于旧的 ADO.NET 代码来获取该值吗?是否真的无法执行任意SQL片段并从上下文中取回一些结果?

看起来执行原始 SQL 不是 EF Core 的优先级,所以到目前为止(EF Core 3.1)它只提供 public 几个基本的有限方法。 FromSql 需要实体类型或 keyless entity type,而 ExecuteSqlRaw / ExecuteSqlInterpolated 是通往 ADO.NET ExecuteNonQuery 的 "modern" 桥梁 returns 受影响的行。

好处是 EF Core 构建在 public 服务架构之上,因此可用于添加一些缺失的功能。例如,服务可用于构建所谓的 IRelationalCommand,它具有所有 DbCommand 执行方法,特别是 SQL 所需的 ExecuteScalar

由于 EF Core 模型支持序列,因此还有一个服务用于构建检索下一个值所需的 IRelationalCommand(由 HiLo 值生成器在内部使用)。

话虽如此,以下是使用上述概念实现相关自定义方法的示例:

using System;
using System.Globalization;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Update;

namespace Microsoft.EntityFrameworkCore
{
    public static partial class CustomExtensions
    {
        public static long GetNextSequenceValue(this DbContext context, string name, string schema = null)
        {
            var sqlGenerator = context.GetService<IUpdateSqlGenerator>();
            var sql = sqlGenerator.GenerateNextSequenceValueOperation(name, schema ?? context.Model.GetDefaultSchema());
            var rawCommandBuilder = context.GetService<IRawSqlCommandBuilder>();
            var command = rawCommandBuilder.Build(sql);
            var connection = context.GetService<IRelationalConnection>();
            var logger = context.GetService<IDiagnosticsLogger<DbLoggerCategory.Database.Command>>();
            var parameters = new RelationalCommandParameterObject(connection, null, null, context, logger);
            var result = command.ExecuteScalar(parameters);
            return Convert.ToInt64(result, CultureInfo.InvariantCulture);
        }
    }
}

如果你想运行一个任意的TSQL批处理和return一个标量值,你可以这样做:

var p = new SqlParameter("@result", System.Data.SqlDbType.Int);
p.Direction = System.Data.ParameterDirection.Output;
context.Database.ExecuteSqlRaw("set @result = next value for some_seq", p);
var nextVal = (int)p.Value;

丑陋,但我能想到的最好的东西:

var connection = repcontext.Database.GetDbConnection();
connection.Open();
using var cmd = connection.CreateCommand();
cmd.CommandText = "SELECT NEXT VALUE FOR AA.TransSeq;";
var obj = cmd.ExecuteScalar();
connection.Close();
seqnum = (int)obj;

在您流畅的 api 配置中,您可以创建将 ID 自动设置为序列

中的下一个值的迁移
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.HasSequence<int>("OrderNumbers");

    modelBuilder.Entity<Order>()
        .Property(o => o.OrderNo)
        .HasDefaultValueSql("NEXT VALUE FOR shared.OrderNumbers");
}

用于创建序列:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.HasSequence<int>("OrderNumbers", schema: "shared")
        .StartsAt(1000)
        .IncrementsBy(5);
}

从这里阅读更多内容:https://www.talkingdotnet.com/use-sql-server-sequence-in-entity-framework-core-primary-key/

对于遇到此问题的 oracle 版本的人,这里有一个解决方案:

var p = new OracleParameter("result", OracleDbType.Decimal, null, System.Data.ParameterDirection.Output);
Database.ExecuteSqlRaw($"BEGIN :result := my_seq.nextval; END;", p);
var nextVal = p.Value;