简体   繁体   中英

How to Generate Custom Key Number Using C# in ASP.NET Core

this my class,

public class PatReg
{
    [DatabaseGenerated(DatabaseGeneratedOption.Computed), ScaffoldColumn(false)]
    public Int64 RecId { get; set; }
    [Key,Display(Name = "File Id"), ScaffoldColumn(true), DatabaseGenerated(DatabaseGeneratedOption.None )]
    public Int64 FileId { get; set; }
    [Required, Display(Name = "First Name")]
    public string FName { get; set; }
    [Required, Display(Name = "Middle Name")]
    public string MName { get; set; }
    [Required, Display(Name = "Last Name")]
    public string LName { get; set; }
    [Required, Display(Name = "Date of Birth"), DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
    public DateTime Dob { get; set; }
  }

"FileId" is my primary key and I want to generate it upon saving the record save it along with the record,

Custom number would have the following spec, YYMMDD0001 where YY is two digit of the year, MM Two digit of the month. DD is two digits of day, 001 is the serial start and reset every day.

This is my controller

// POST: PatReg/Create
    // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
    // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Create([Bind("FileId,FName,MName,LName,Dob")] PatReg patReg)
    {

        if (ModelState.IsValid)
        {
            _context.Add(patReg);
            await _context.SaveChangesAsync();
            return RedirectToAction("Index");
        }
        return View(patReg);
    }

Background

I used to generate this number using SQL stored procedure like below,

 Set @YY = (RIGHT(CONVERT(VARCHAR(8), GETDATE(), 1),2))
    Set @MM =  SUBSTRING(CONVERT(nvarchar(6),getdate(), 112),5,2)
    Set @DD =  RIGHT('00' + CONVERT(NVARCHAR(2), DATEPART(DAY, GETDATE())), 2)
    Set @SRL = (SELECT FileNumSrl FROM SetTblSrls WHERE RecID = 1)
    SET @FileId =(select CAST(CONCAT ( @YY , @MM , @DD,00 ,@SRL) AS int))

"@SRL" represents the serial sequence that I lookup from "SetTblSrls" and I used to have a trigger on the target table to update this number on each insert by which i get a new number every time I generate the FileId

How can I do it using EF and C#,

You're going to need to persist a sequence number somewhere so that you can safely increment it each time you've got a new file. In-memory isn't really an option since IIS resets its app pools every 29 hours by default, losing anything you may have cached. As such you're left with the database or the file system.

The following SQL provides you with a safe and performant means of getting the next available sequence number by simply executing the stored procedure from your server-side C# code and reading the value returned:

create table DailySequence
(
    SequenceDate date not null primary key,
    LastSequence int not null default(0) -- Change the default if you want your first sequence to be 1
)
go

create procedure dbo.GetNextSequence as
begin
    Declare @today date = getdate()
    Declare @table table (id int)

    begin tran

    update DailySequence set LastSequence=LastSequence+1 output inserted.LastSequence into @table where SequenceDate=@today

    if (@@ROWCOUNT=0)
        insert into DailySequence(SequenceDate) 
        output inserted.LastSequence into @table 
        values (@today)

    commit

    declare @s varchar(20) 
    select @s = convert(varchar(20), id) from @table

    if (Len(@s)<4)
        set @s = Right('000' + @s, 4)

    select CONVERT(VARCHAR(10), @today, 12) + @s [Sequence]
end
go

This solution is based on the uses of SQL Server Sequences (available since SQL Server 2012 and for Azure SQL Database ). If you cannot use them, you can skip to another answer.


This solution consists of creating a sequence that will compute the FileId automatically. But you will need to reset the sequence every day at midnight to achieve what you want. Here is how you can create the sequence:

Set @YY = (RIGHT(CONVERT(VARCHAR(8), GETDATE(), 1),2))
Set @MM =  SUBSTRING(CONVERT(nvarchar(6),getdate(), 112),5,2)
Set @DD =  RIGHT('00' + CONVERT(NVARCHAR(2), DATEPART(DAY, GETDATE())), 2)

DROP SEQUENCE IF EXISTS dbo.DailyFileId;
CREATE SEQUENCE dbo.DailyFileId
    START WITH CAST(CONCAT(@YY, @MM, @DD, '0001') AS int)
    INCREMENT BY 1;  
GO  

(Or something like that, I do not have a SQL Server engine to test them. Please do not hesite to fix them in the comments if required)

To run the script every day, you can use a SQL Agent. It is in my opinion the best option, but you can also run a new thread within your application that will run the script every day.

If you prefer that option, here is how you can do it. I will let you decide where you need to put that code in your application:

// Call that method as close as you can from the startup of your application
Task.Run(() => DailyResetSequence());

private async void DailyResetSequence()
{
    while (true)
    {
        using (var dbContext = new DbContext())
        {
            var tomorrow = DateTime.Now.AddDays(1);
            var sleepingTime = tomorrow - DateTime.Now;

            // waiting until tomorrow morning
            await Task.Delay(sleepingTime);

            // See below
            dbContext.ResetSequence();
        }
    }
}

(Please note that I do not handle the closure of your application. You probably need to cancel the task at that moment, and probably some other stuff like that)

Once your sequence has been created, you just have to query that sequence to get your new file id. The SQL Engine will automatically handle concurrent calls and ensures each returned id is unique.

It looks like we cannot execute raw queries with EF Core like we could used to do with EF6 ( dbContext.Data.SqlQuery ). One solution would be to execute manually a sql command. I don't know how these operations (get the connection, opening it, etc) are thread safe so I prefer to be safe and use a lock mecanism:

static class DbContextExtensions
{
    private static object DbContextLock = new object();

    public static void ResetSquence(this DbContext dbContext)
    {
        lock (DbContextLock)
        {
            using (var command = dbContext.Database.GetDbConnection().CreateCommand())
            {
                command.CommandText = @"Set @YY = (RIGHT(CONVERT(VARCHAR(8), GETDATE(), 1),2))
Set @MM =  SUBSTRING(CONVERT(nvarchar(6),getdate(), 112),5,2)
Set @DD =  RIGHT('00' + CONVERT(NVARCHAR(2), DATEPART(DAY, GETDATE())), 2)

DROP SEQUENCE IF EXISTS dbo.DailyFileId;
CREATE SEQUENCE dbo.DailyFileId
START WITH CAST(CONCAT(@YY, @MM, @DD, '0001') AS int)
INCREMENT BY 1;  
GO  ";
                command.CommandType = CommandType.Text;

                dbContext.Database.OpenConnection();

                command.ExecuteNonQuery();

                dbContext.Database.CloseConnection();
            }
        }
    }

    public static long GetNextFileId(this DbContext dbContext)
    {
        long fileId;

        lock (DbContextLock)
        {
            using (var command = dbContext.Database.GetDbConnection().CreateCommand())
            {
                command.CommandText = "SELECT NEXT VALUE FOR dbo.DailyFileId;";
                command.CommandType = CommandType.Text;

                dbContext.Database.OpenConnection();

                fileId = (long)command.ExecuteScalar();

                dbContext.Database.CloseConnection();
            }
        }

        return fileId;
    }
}

(Same, I cannot test it so do not hesite to share fixes/improvments in the comments if required)

The method is an extension methods so you just have to call it that way:

var newFileId = dbContext.GetNextFileId();

To do this, you will need to install: Microsoft.EntityFrameworkCore.Relational .

Please Your Input is highly appreciated, So I solved it with a lot of help from @Pete,

My SP model Class,

public class FileIdSeq

{
    [Key]
    public DateTime SequenceDate { get; set; }
    [DefaultValue(1)]
    public int LastSequence { get; set; }
}

My SQL-SP,

 USE [ARTCORE] GO /****** Object: StoredProcedure [dbo].[FileIdSeqSP] Script Date: 08/04/2017 10:19:24 PM ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO ALTER PROCEDURE [dbo].[FileIdSeqSP] AS BEGIN Declare @today date = getdate() Declare @table table (id int) SET NOCOUNT ON; If Exists(select * from information_schema.columns where table_name = 'FileIdSeq' and column_name = 'LastSequence' and Table_schema = 'dbo' and column_default is NULL) BEGIN ALTER TABLE [dbo].FileIdSeq ADD DEFAULT (1) FOR LastSequence END BEGIN TRAN UPDATE FileIdSeq SET LastSequence = LastSequence + 1 output inserted.LastSequence into @table where SequenceDate=@today if (@@ROWCOUNT=0) INSERT INTO FileIdSeq (SequenceDate) output inserted.LastSequence into @table VALUES (@today) commit declare @s varchar(20) select @s = convert(varchar(20), id) from @table if (Len(@s)<4) set @s = Right('000' + @s, 4) SELECT Cast(CONVERT(VARCHAR(10), @today, 12) + @s as int) as LastSequence, SequenceDate FROM FileIdSeq WHERE (SequenceDate = @today) END 

My Table Class (Model), At which the custom fileId will be generated in,

 public class PatReg
{
    [NotMapped]
    private Int64 _FileId;
    [Key, Display(Name = "File Id"), ScaffoldColumn(false), DatabaseGenerated(DatabaseGeneratedOption.None)]
    public Int64 FileId
    {
        get
        {
            return this._FileId;
        }
        set
        {
            this._FileId = value;
        }
    }
    [Required, Display(Name = "First Name")]
    public string FName { get; set; } 
    [Required, Display(Name = "Middle Name")]
    public string MName { get; set; }
    [Required, Display(Name = "Last Name")]
    public string LName { get; set; }
    [Required, Display(Name = "Date of Birth"), DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
    public DateTime Dob { get; set; }

}

The controller,

public Int64 GetSerial()
    {
        List<FileIdSeq> NewFileSeq = new List<FileIdSeq>();
        NewFileSeq = _context.FileIdSeq.FromSql("FileIdSeqSP").ToList();
        var FileID = NewFileSeq[0].LastSequence;
        return FileID;
    }
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Create([Bind("FName,MName,LName,Dob")] PatReg patReg)
    {
        if (ModelState.IsValid)
        {
            patReg.FileId = GetSerial();
            _context.Add(patReg);
            await _context.SaveChangesAsync();
            return RedirectToAction("Index");
        }
        return View(patReg);
    }

GetSerial() Generates the Sequence by calling the stored procedure and returning the LastSequence From the strongly typed list

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM