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 #,
source to share
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
source to share
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 .
source to share
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
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
source to share