如何在 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
来生成序列
这是我的 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
来生成序列