如何在 ASP.NET Core 中使用 C# 生成自定义键号

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

这是我的 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" 是我的主键,我想在保存记录时生成它,将它与记录一起保存,

自定义号码将具有以下规范,YYMMDD0001,其中 YY 是年份的两位数,MM 是月份的两位数。 DD是天的两位数,001是串口开始每天重新设置

这是我的控制器

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

背景

我曾经使用 SQL 存储过程生成这个数字,如下所示,

 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" 表示我从 "SetTblSrls" 查找的序列序列,我曾经在目标 table 上有一个触发器来更新每个插入的这个数字,通过它我得到一个新的每次生成 FileId

时的编号

我如何使用 EF 和 C# 实现,

您将需要在某处保留一个序列号,以便您可以在每次获得新文件时安全地递增它。内存中并不是真正的选择,因为默认情况下 IIS 每 29 小时重置一次其应用程序池,从而丢失您可能缓存的任何内容。因此,您只剩下数据库或文件系统了。

以下 SQL 通过简单地从服务器端 C# 代码执行存储过程并读取返回值,为您提供了一种安全且高效的方法来获取下一个可用序列号:

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

此解决方案基于 SQL 服务器序列 的使用(自 SQL Server 2012 起可用Azure SQL 数据库)。如果你不能使用它们,你可以跳到另一个答案。


此解决方案包括创建一个将自动计算 FileId 的序列。但是您需要每天午夜重新设置顺序才能达到您想要的效果。以下是创建序列的方法:

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  

(或者类似的东西,我没有 SQL 服务器引擎来测试它们。如果需要,请不要犹豫,在评论中修复它们)

每天要运行脚本,可以使用SQL代理。在我看来,这是最好的选择,但您也可以 运行 在您的应用程序中创建一个每天都会 运行 脚本的新线程。

如果您更喜欢该选项,请按以下步骤操作。我会让你决定你需要把代码放在你的应用程序中的什么地方:

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

(请注意,我不处理您的应用程序的关闭。您可能需要在那一刻取消任务,可能还有其他类似的事情)

创建序列后,您只需查询该序列即可获取新的文件 ID。 SQL 引擎将自动处理并发调用并确保每个返回的 ID 都是唯一的。

看起来我们无法像过去使用 EF6 那样使用 EF Core 执行原始查询 (dbContext.Data.SqlQuery)。一种解决方案是手动执行 sql 命令。我不知道这些操作(获取连接、打开它等)如何是线程安全的,所以我更喜欢安全并使用锁机制:

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

(相同,我无法测试,所以如果需要,请不要犹豫,在评论中分享 fixes/improvments)

该方法是一个扩展方法,因此您只需这样调用它:

var newFileId = dbContext.GetNextFileId();

为此,您需要安装:Microsoft.EntityFrameworkCore.Relational

非常感谢您的意见, 所以我在@Pete 的帮助下解决了这个问题,

我的SP型号Class,

public class FileIdSeq

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

我的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

我的TableClass(型号),将在其中生成自定义文件ID,

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

}

控制器,

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() 通过调用存储过程并从强类型列表

返回 LastSequence 来生成序列