使用EF Core 3.1获得SQL Server序列的“下一个值”-不可能?

06odsfpq  于 2023-01-08  发布在  SQL Server
关注(0)|答案(6)|浏览(228)

我正在编写一个新的ASP.NET Core Web API,我的需求之一是能够利用EF Core 3.1获取SQL Server中定义的sequence的下一个值,作为我需要存储的记录的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方法-但由于我只需要返回序列的下一个值(一个INT),这是行不通的。我还知道这些方法也存在于context.Database级别,看起来它非常接近我在EF 6.x中的方法-但在这里,这些方法返回受影响的行数-我还没有找到从SEQUENCE发回新值的方法。
在EF Core 3.x中,我真的必须求助于古老的ADO .NET代码来获取那个值吗?真的没有办法执行任意的SQL片段并从上下文中获取一些结果吗?

798qvoo8

798qvoo81#

如果要运行任意TSQL批处理并返回标量值,可以按如下方式执行:

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;
lstz6jyr

lstz6jyr2#

看起来执行原始SQL不是EF Core的优先事项,所以到目前为止(EF Core 3.1),它只公开提供了几个基本的有限方法。FromSql需要实体类型或keyless entity typeExecuteSqlRaw/ExecuteSqlInterpolated是到ADO .NET ExecuteNonQuery的“现代”桥梁,后者返回受影响的行。
好的方面是EF Core构建在公共服务架构之上,因此它可以用来添加一些缺失的功能。例如,服务可以用来构建所谓的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);
        }
    }
}
pbpqsu0x

pbpqsu0x3#

在您的Fluent API配置中,您可以创建将ID自动设置为Sequence中的下一个值的迁移

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/

2nc8po8w

2nc8po8w4#

对于甲骨文版本的这个问题的痛苦的人,这里有一个解决方案:

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;
nkcskrwz

nkcskrwz5#

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

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;
1tuwyuhd

1tuwyuhd6#

此代码应适用于各种情况:

public static class DbSequence
{
    private const string sqlCode = "SELECT NEXT VALUE FOR {0}.{1};";

    public static T GetNextSeq<T>(this DbContext dbContext, string seqName)
    {
        var sqlCnn = dbContext.Database.GetDbConnection();
        bool cnnClosed = sqlCnn.State != ConnectionState.Open;
        if (cnnClosed) sqlCnn.Open();
        try
        {
            using (var sqlCmd = sqlCnn.CreateCommand())
            {
                sqlCmd.Transaction = dbContext.Database.CurrentTransaction?.GetDbTransaction();
                sqlCmd.CommandText = string.Format(sqlCode, "dbo", seqName);
                var result = sqlCmd.ExecuteScalar();
                if ((result == null) || (result == DBNull.Value)) throw new InvalidOperationException();
                return (T)result;
            }
        }
        finally
        {
            if (cnnClosed) sqlCnn.Close();
        }
    }
}

相关问题