How to create a custom key number using C # in ASP.NET core

this class of mine,

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 when saving the record, save it along with the record,

The custom number will have the following specification: YYMMDD0001 where YY is two digits for the year, MM Two digits for the month. DD is two digits of the day, 001 is 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 create this number using SQL Stored Procedure as shown 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 sequential sequence that I am traversing from "SetTblSrls" and I used a trigger on the target table to update that number on every insert with which I get a new number every time I create a FileId

How to do it using EF and C #,

+3


source to share


3 answers


You will need to store the sequence number somewhere so you can safely increment it every time you have a new file. In-memory is not really an option, as IIS resets its application pools every 29 hours by default, losing everything you can cache. Thus, you are left with the database or file system.

The following SQL provides you with a safe and efficient means to get the next available sequence number by simply executing the stored procedure from your server-side C # code and reading the return value:



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

      

+1


source


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


This solution consists of creating a sequence that will automatically calculate the FileId. But you will need to reset the sequence every day at midnight to achieve what you want. This is how you can create a 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 don't have a SQL Server mechanism to test them. Please don't use it to fix it in the comments if necessary)

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

If you prefer this option, here's how you can do it. I'll let you decide where you need to put this 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 am not handling closing your application. You probably need to cancel the task at this point and maybe some other similar stuff)

Once your sequence has been created, you just need to request that sequence to get your new file ID. SQL Engine automatically handles concurrent calls and ensures that each returned identifier is unique.

It looks like we cannot execute raw queries with EF Core like we could use EF6 ( dbContext.Data.SqlQuery

). One solution would be to manually execute the sql command. I don't know how these operations (get a connection, open it, etc.) are thread safe, so I prefer to be safe and use mecanism lock:

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

      

(Ditto, I can't test it, so don't use it to share fixes / improvements in comments if needed)

Method are extension methods, so you just need to call it like this:

var newFileId = dbContext.GetNextFileId();

      

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

+1


source


Please, your input is much 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
      

Run codeHide result


My table class (model) in which the custom fileId will be created,

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

}

      

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 a sequence by calling a stored procedure and returning LastSequence

From a strongly typed list

0


source







All Articles