42

I want to make use SQL Server sequence objects in Entity Framework to show number sequence before save it into database.

In current scenario I'm doing something related by increment by one in stored procedure (previous value stored in one table) and passing that value to C# code.

To achieve this I needed one table but now I want to convert it to a sequence object (will it give any advantage ?).

I know how to create sequence and get next value in SQL Server.

But I want to know how to get next value of sequence object of SQL Server in Entity Framework?

I am to unable to find useful answers in Related questions in SO.

Thanks in advance.

1

10 Answers 10

52

You can create a simple stored procedure in SQL Server that selects the next sequence value like this:

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

and then you can import that stored procedure into your EDMX model in Entity Framework, and call that stored procedure and fetch the sequence value like this:

// 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);
}

Update: actually, you can skip the stored procedure and just run this raw SQL query from your EF context:

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;
    }
}
Sign up to request clarification or add additional context in comments.

7 Comments

@TimPohlmann: just run the SQL statement I show in the "Update" section - no stored procedure or anything needed
But I have to generate dbo first. Don't I?
You need to create the dbo.TestSequence as a SEQUENCE first - yes
If you're using migrations; you can add your CREATE SEQUENCE script to a migration as a Sql("CREATE SEQUENCE ......."); call
how do you handle it with transaction ?
|
33

Since I am using Code First and I do not want to have some additional DDL, this is my way: (EF Core 2.1, SQL Server)

Define the sequence:

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

And to retrieve it I add the following function to the context:

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

2 Comments

How do you map this in an Entity?
What do you mean? You wan to use it as a primary key?
13

In case anyone else who is working with Entity Framework Core ends up looking here, this worked for me:

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

1 Comment

I had a problem with ExecuteSqlRaw where it just returns -1 with any queries whereas this ADO.NET code works well.
13

EF 6.0: modify DbContext class, adding NextValueForSequence function. Code-first creation of sequences (default settings):

using Microsoft.EntityFrameworkCore;
using Microsoft.Data.SqlClient;
using System.ComponentModel;

public class myContext : DbContext
{
     protected override void OnModelCreating(ModelBuilder modelBuilder)
     {
         modelBuilder.HasSequence<int>("Generate_First_Sequence");
         modelBuilder.HasSequence<int>("Generate_Second_Sequence");
         
         // other ModelBuilder calls as needed
         // ...
     }

     // DbSet declarations as needed
     // ...

     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,
    }
}

While calling context

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

Hope this helps :)

1 Comment

If you get exception with message "The SqlParameterCollection only accepts non-null SqlParameter type objects, not SqlParameter objects", check your namespace imports and make sure you have using Microsoft.Data.SqlClient;. See this answer: stackoverflow.com/a/63968315/6621862. Just realized the using statement is already in this answer :facepalm:
3

Since this functionality doesn't come out of the box, I came up to write an extension-class for the DbContext that does the job. Have a look at this chunk of code:

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

While I must admit that all that stuff with reflection and annotations for some might seem like an overkill, I still kinda like it.

It allows me to retrieve the value in a pretty elegant way

ctx.NextValueForSequence(Sequence.OrderNr);

It also mocks a "type proof" way, constraining me to explicitly define the different sequence names in a centralized location rather than just passing magic strings from anywhere I want.

If you don't want it that way, just change the method in order to pass the sequence name as a string. It would work just as fine.

Comments

3

As of today imo there's more elegant solution. Wat I disliked about available answers - hardcoded sequence names. Therefore, sharing my solution:

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();
}

It is based on the fact that now we can generate SELECTs to retrieve next from the sequence: https://github.com/dotnet/efcore/issues/8403#issuecomment-1457919371

1 Comment

You still have to hard-code the sequence name :). The benefit of this code is probably that it's database-provider agnostic.
1

The updated answer that marc_s gave is missing a task.wait() after the "rawQuery.SingleAsync();". Without a wait() could cause a race condition, attempting to get the results before the query is actually ran in an asynchronous (multi-threaded) process.

3 Comments

This should be a comment, not an answer.
I wanted to comment, but I have not crossed 50 points yet so I couldn't. I then tried to edit and add the necessary code, but couldn't do that either. I did think it was valuable to mention because the bug introduced into the code would have been intermittent and extremely difficult to locate.
Not an answer, and not true, because the answer calls task.Result. (It's not about how to use TPL best).
0

If you wanted to do it outside of a stored procedure you can create an entity class that holds just a string or int (whatever your sequence returns) and then run some raw SQL against it. Then just use your object or string however you'd like.

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

1 Comment

DUAL is an ORACLE syntax.
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;
}

Comments

0

11 years later :) In EF 6.0 you can supplement

modelBuilder.HasSequence<int>("ApplicationNumber")
            .StartsAt(100000)
            .IncrementsBy(1);

with

entity.Property(x => x.ApplicationNumber).HasDefaultValueSql("NEXT VALUE FOR ApplicationNumber");

in the OnModelCreating. No need for extra SQL commands, procedures or extension methods 👍

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.