如何在实体框架中获取SQL Server序列的下一个值?

问题描述 投票:0回答:9

我想在实体框架中使用 SQL Server

sequence
对象在将其保存到数据库之前显示编号规则。

在当前场景中,我正在执行一些相关操作,在存储过程中加一(先前的值存储在一个表中)并将该值传递给 C# 代码。

为了实现这一目标,我需要一个表,但现在我想将其转换为

sequence
对象(它会带来任何优势吗?)。

我知道如何在 SQL Server 中创建序列并获取下一个值。

但是我想知道如何在实体框架中获取 SQL Server

sequence
对象的下一个值?

我无法在SO中的相关问题中找到有用的答案。

提前致谢。

c# sql-server entity-framework orm sql-server-2012
9个回答
51
投票

您可以在 SQL Server 中创建一个简单的存储过程来选择下一个序列值,如下所示:

CREATE PROCEDURE dbo.GetNextSequenceValue 
AS 
BEGIN
    SELECT NEXT VALUE FOR dbo.TestSequence;
END

然后您可以将该存储过程导入到实体框架中的 EDMX 模型中,然后调用该存储过程并获取序列值,如下所示:

// get your EF context
using (YourEfContext ctx = new YourEfContext())
{
    // call the stored procedure function import   
    var results = ctx.GetNextSequenceValue();

    // from the results, get the first/single value
    int? nextSequenceValue = results.Single();

    // display the value, or use it whichever way you need it
    Console.WriteLine("Next sequence value is: {0}", nextSequenceValue.Value);
}

更新:实际上,您可以跳过存储过程,只从 EF 上下文运行这个原始 SQL 查询:

public partial class YourEfContext : DbContext 
{
    .... (other EF stuff) ......

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

        return nextVal;
    }
}

31
投票

由于我使用的是 Code First 并且我不想有一些额外的 DDL,所以这是我的方式:(EF Core 2.1,SQL Server)

定义顺序:

protected override void OnModelCreating( ModelBuilder modelBuilder )
{
    modelBuilder.HasSequence("MySequence");
}

为了检索它,我将以下函数添加到上下文中:

public int GetMySequence()
{
   SqlParameter result = new SqlParameter("@result", System.Data.SqlDbType.Int)
   {
      Direction = System.Data.ParameterDirection.Output
   };

   Database.ExecuteSqlCommand(
              "SELECT @result = (NEXT VALUE FOR MySequence)", result);

   return (int)result.Value;
}

13
投票

如果使用 Entity Framework Core 的其他人最终都在这里查找,这对我有用:

var connection = dbContext.Database.GetDbConnection();
connection.Open();
using (var cmd = connection.CreateCommand())
{
    cmd.CommandText = "SELECT NEXT VALUE FOR ACH.FileIDModifier;";
    var obj = cmd.ExecuteScalar();
    int anInt = (int)obj;
}

12
投票

EF 3.1:在 DbContext 中添加以下函数。参考使用 Microsoft.Data.SqlClient;

 public async Task<int> NextValueForSequence(SequenceEnum.Sequence sequence)
    {
        SqlParameter result = new SqlParameter("@result", System.Data.SqlDbType.Int)
        {
            Direction = System.Data.ParameterDirection.Output
        };

        var sequenceIdentifier = sequence.GetType().GetMember(sequence.ToString()).First().GetCustomAttribute<DescriptionAttribute>().Description;

        await Database.ExecuteSqlRawAsync($"SELECT @result = (NEXT VALUE FOR [{sequenceIdentifier}])", result);

        return (int)result.Value;
    }



public class SequenceEnum
{
    public enum Sequence
    {
        [Description("Generate_First_Sequence")]
        FirstSequence,
        [Description("Generate_Second_Sequence")]
        SecondSequence,
    }
}

调用上下文时

await context.NextValueForSequence(SequenceEnum.Sequence.FirstSequence);

希望这有帮助:)


2
投票

由于此功能不是现成的,所以我想为完成这项工作的 DbContext 编写一个扩展类。看看这段代码:

public enum Sequence
{
    [Description("sequence__name__goes__here")]
    ClientNr,
    [Description("another__sequence__name")]
    OrderNr,
}
public static class MyDbContextExtensions
{
    public static int NextValueForSequence(this MyDbContext pCtx, Sequence pSequence)
    {
        SqlParameter result = new SqlParameter("@result", System.Data.SqlDbType.Int)
        {
            Direction = System.Data.ParameterDirection.Output
        };
        var sequenceIdentifier = pSequence.GetType()
                    .GetMember(pSequence.ToString())
                    .First()
                    .GetCustomAttribute<DescriptionAttribute>()
                    .Description;
        pCtx.Database.ExecuteSqlCommand($"SELECT @result = (NEXT VALUE FOR [{sequenceIdentifier}]);", result);
        return (int)result.Value;
    }
}

虽然我必须承认,所有带有反射和注释的东西对某些人来说可能看起来有些过分,但我仍然有点喜欢它。

它允许我以一种非常优雅的方式检索值

ctx.NextValueForSequence(Sequence.OrderNr);

它还模拟了一种“类型证明”方式,限制我在集中位置显式定义不同的序列名称,而不是仅仅从我想要的任何地方传递魔术字符串。

如果您不希望这样,只需更改方法以便将序列名称作为字符串传递。它会同样有效。


1
投票

marc_s 给出的更新答案在“rawQuery.SingleAsync();”之后缺少一个 task.wait()。如果没有 wait() 可能会导致竞争条件,尝试在异步(多线程)进程中实际运行查询之前获取结果。


0
投票

如果您想在存储过程之外执行此操作,您可以创建一个仅包含字符串或整数(无论您的序列返回什么)的实体类,然后对其运行一些原始 SQL。然后只需使用您想要的对象或字符串即可。

 SEQ_TXN_ID txn_id= _context.SEQ_TXN_IDs.SqlQuery("SELECT txn_id_seq.NEXTVAL txn_ID FROM DUAL").FirstOrDefault();

0
投票
//This code worked for me after slight mods of other answers here. Try this:
public static Int64 GetNextFormSequenceValue(){
    Int64 result = 0;
    var db = new dbContext.db(); //change the name to whatever your LINQ names are
    Int64 nextId = db.ExecuteQuery<Int64>("SELECT NEXT VALUE FOR YOURSEQNAME").FirstOrDefault();

    if(nextId > 0){
      result = nextId;
    }
    return result;
}

0
投票

从今天开始,我认为有更优雅的解决方案。我不喜欢可用的答案 - 硬编码的序列名称。因此,分享我的解决方案:

public async Task<int> GetNextValueAsync(string sequenceName, string? schema = null)
{
    var sqlGenerator = this.GetService<IUpdateSqlGenerator>();
    var sql = sqlGenerator.GenerateNextSequenceValueOperation(sequenceName, schema);

    var result = await Database.SqlQueryRaw<int>(sql).ToListAsync();
    return result.Single();
}

基于这样一个事实,现在我们可以生成 SELECT 来从序列中检索下一个: https://github.com/dotnet/efcore/issues/8403#issuecomment-1457919371

© www.soinside.com 2019 - 2024. All rights reserved.