对递增值的方法的异步调用避免重复
Async call to a method which increments a value avoid duplicates
我正在尝试从 AAA000 -> ZZZ999 调用创建 ID 的方法。该方法本身工作正常,但我将其称为异步,这会在同时创建 2 个或更多个案例时引起问题。最高值存储在数据库中(不必如此),我正在尝试这样做:
获取当前最高值 -> 使用我的方法增加它 -> 保存新的最高值 -> Return 新的最高值。
在使用此方法之前,我只是从案例 table 中获取最大值。
我考虑了几种实现此目的的方法,包括从 AAA000 -> ZZZ999 播种数据库,并查找我增加的 postgres ID 以获得 'friendly' 值。然而,这似乎不是最理想的。
任何关于如何实现这一目标的建议将不胜感激:
代码概览:
.net 核心微服务
CQRS
亚马逊 SQS
地铁
自动映射器
MediatR
消费者
public async Task Consume(ConsumeContext<EmailClassified> context)
{
var caseCommand = new CreateCaseFromEmailCommand(context.Message.Classification, context.Message.AiProbability, context.Message.Email);
var newCase = await _mediator.Send(caseCommand);
if (newCase != null)
{
// do something signalR
await Task.CompletedTask;
}
else
{
// log error
await Task.CompletedTask;
}
}
命令
public class CreateCaseFromEmailCommand : IRequest<Case>
{
public string Classification { get; set; }
public decimal AiProbability { get; set; }
public CaseEmail Email { get; set; }
public CreateCaseFromEmailCommand(string classification, decimal aiProbability, CaseEmail email)
{
Classification = classification;
AiProbability = aiProbability;
Email = email;
}
}
处理程序
public class CreateCaseFromEmailCommandHandler : IRequestHandler<CreateCaseFromEmailCommand, Case>
{
private readonly IMyDbContext _context;
public CreateCaseFromEmailCommandHandler(IMyDbContext context)
{
_context = context;
}
public async Task<Case> Handle(CreateCaseFromEmailCommand request, CancellationToken cancellationToken)
{
try
{
NextIdHelper helper = new NextIdHelper(_context);
string caseReference = helper.GetNextFriendlyId();
var investor = _context.Set<Investor>()
.FirstOrDefault(i => i.EmailAddress.ToLower() == request.Email.FromAddress.ToLower());
var classification = _context.Set<Classification>().Include(t => t.Team)
.FirstOrDefault(c => c.Name.ToLower() == request.Classification.ToLower());
var team = classification.Team;
var entity = new Case()
{
FriendlyCaseId = caseReference,
Summary = request.Email.Subject,
Description = request.Email.Message,
Priority = Priority.Medium,
CaseStatus = CaseStatus.Open,
CreatedDate = DateTime.Now,
NextSla = DateTime.Now.AddDays(1),
Investor = investor,
UnknownInvestorEmail = investor == null ? request.Email.FromAddress : null,
Classification = classification,
Team = team,
AiProbability = request.AiProbability,
Emails = new List<CaseEmail> {request.Email}
};
_context.Set<Case>().Add(entity);
await _context.SaveChangesAsync(cancellationToken);
return entity;
}
catch (Exception ex)
{
return null;
}
}
帮手
public class NextIdHelper
{
private readonly IMyDbContext _context;
public string Value { get; set; }
public NextIdHelper(IMyDbContext context)
{
_context = context;
}
public string GetNextFriendlyId()
{
var nextValue = "";
var highestId = _context.Set<MaxCaseReference>().FirstOrDefault();
if (!string.IsNullOrWhiteSpace(Value))
{
nextValue = CalculateNextId(Value);
}
else
{
nextValue = CalculateNextId(highestId.Reference);
}
highestId.Reference = nextValue;
Value = nextValue;
_context.SaveChanges();
return nextValue;
}
private string CalculateNextId(string currentId)
{
char[] characters = currentId.ToCharArray();
int currNum = int.Parse(currentId.Substring(currentId.Length - 3));
Tuple<char, char, char, int> id = new Tuple<char, char, char, int>(characters[0], characters[1], characters[2], currNum);
var number = id.Item4 + 1;
var c3 = id.Item3;
var c2 = id.Item2;
var c1 = id.Item1;
if (number > 999)
{
number = 0;
c3++;
if (c3 > 'Z')
{
c3 = 'A';
c2++;
if (c2 > 'Z')
{
c2 = 'A';
c1++;
if (c1 > 'Z')
{
throw new IndexOutOfRangeException("Next ID bigger than \"ZZZ999\"");
}
}
}
}
var next = new Tuple<char, char, char, int>(c1, c2, c3, number);
return $"{next.Item1}{next.Item2}{next.Item3}{next.Item4.ToString().PadLeft(3, '0')}";
}
}
不是选择和递增字符串,而是生成一个带有 SEQUENCE 或 IDENTITY 列的 ID,如果必须,在客户端将其转换为字符串。 EG
static string GetId(int id)
{
var chars = new char[6];
char Zero = '0';
chars[5] = (char) (Zero + id % 10);
id = id / 10;
chars[4] = (char)(Zero + id % 10);
id = id / 10;
chars[3] = (char)(Zero + id % 10);
id = id / 10;
char A = 'A';
chars[2] = (char)(A + id % 26);
id = id / 26;
chars[1] = (char)(A + id % 26);
id = id / 26;
chars[0] = (char)(A + id % 26);
return new string(chars);
}
要获取序列值,请像这样向 DbContext 添加一个方法:
private long GetSequenceNextVal(string sequenceName)
{
var con = Database.GetDbConnection();
var initialState = con.State;
if (con.State != System.Data.ConnectionState.Open)
con.Open(); //the DbContext will clean up after exceptions
var cmd = con.CreateCommand();
var connectionType = con.GetType().Name;
string sql;
if (connectionType == "SqlConnection")
{
sql = $"select next value for {sequenceName};";
}
else if (connectionType == "NpgsqlConnection")
{
sql = $"SELECT nextval('{sequenceName}');";
}
else if (connectionType == "OracleConnection")
{
sql = $"select {sequenceName}.nextval from dual";
}
else
{
throw new NotImplementedException($"No sequence generatator for {connectionType}");
}
cmd.CommandText = sql;
var result = cmd.ExecuteScalar();
var val = Convert.ToInt64(result);
if (initialState == System.Data.ConnectionState.Closed)
con.Close();
return val;
}
我正在尝试从 AAA000 -> ZZZ999 调用创建 ID 的方法。该方法本身工作正常,但我将其称为异步,这会在同时创建 2 个或更多个案例时引起问题。最高值存储在数据库中(不必如此),我正在尝试这样做:
获取当前最高值 -> 使用我的方法增加它 -> 保存新的最高值 -> Return 新的最高值。
在使用此方法之前,我只是从案例 table 中获取最大值。
我考虑了几种实现此目的的方法,包括从 AAA000 -> ZZZ999 播种数据库,并查找我增加的 postgres ID 以获得 'friendly' 值。然而,这似乎不是最理想的。
任何关于如何实现这一目标的建议将不胜感激:
代码概览:
.net 核心微服务
CQRS
亚马逊 SQS
地铁
自动映射器
MediatR
消费者
public async Task Consume(ConsumeContext<EmailClassified> context)
{
var caseCommand = new CreateCaseFromEmailCommand(context.Message.Classification, context.Message.AiProbability, context.Message.Email);
var newCase = await _mediator.Send(caseCommand);
if (newCase != null)
{
// do something signalR
await Task.CompletedTask;
}
else
{
// log error
await Task.CompletedTask;
}
}
命令
public class CreateCaseFromEmailCommand : IRequest<Case>
{
public string Classification { get; set; }
public decimal AiProbability { get; set; }
public CaseEmail Email { get; set; }
public CreateCaseFromEmailCommand(string classification, decimal aiProbability, CaseEmail email)
{
Classification = classification;
AiProbability = aiProbability;
Email = email;
}
}
处理程序
public class CreateCaseFromEmailCommandHandler : IRequestHandler<CreateCaseFromEmailCommand, Case>
{
private readonly IMyDbContext _context;
public CreateCaseFromEmailCommandHandler(IMyDbContext context)
{
_context = context;
}
public async Task<Case> Handle(CreateCaseFromEmailCommand request, CancellationToken cancellationToken)
{
try
{
NextIdHelper helper = new NextIdHelper(_context);
string caseReference = helper.GetNextFriendlyId();
var investor = _context.Set<Investor>()
.FirstOrDefault(i => i.EmailAddress.ToLower() == request.Email.FromAddress.ToLower());
var classification = _context.Set<Classification>().Include(t => t.Team)
.FirstOrDefault(c => c.Name.ToLower() == request.Classification.ToLower());
var team = classification.Team;
var entity = new Case()
{
FriendlyCaseId = caseReference,
Summary = request.Email.Subject,
Description = request.Email.Message,
Priority = Priority.Medium,
CaseStatus = CaseStatus.Open,
CreatedDate = DateTime.Now,
NextSla = DateTime.Now.AddDays(1),
Investor = investor,
UnknownInvestorEmail = investor == null ? request.Email.FromAddress : null,
Classification = classification,
Team = team,
AiProbability = request.AiProbability,
Emails = new List<CaseEmail> {request.Email}
};
_context.Set<Case>().Add(entity);
await _context.SaveChangesAsync(cancellationToken);
return entity;
}
catch (Exception ex)
{
return null;
}
}
帮手
public class NextIdHelper
{
private readonly IMyDbContext _context;
public string Value { get; set; }
public NextIdHelper(IMyDbContext context)
{
_context = context;
}
public string GetNextFriendlyId()
{
var nextValue = "";
var highestId = _context.Set<MaxCaseReference>().FirstOrDefault();
if (!string.IsNullOrWhiteSpace(Value))
{
nextValue = CalculateNextId(Value);
}
else
{
nextValue = CalculateNextId(highestId.Reference);
}
highestId.Reference = nextValue;
Value = nextValue;
_context.SaveChanges();
return nextValue;
}
private string CalculateNextId(string currentId)
{
char[] characters = currentId.ToCharArray();
int currNum = int.Parse(currentId.Substring(currentId.Length - 3));
Tuple<char, char, char, int> id = new Tuple<char, char, char, int>(characters[0], characters[1], characters[2], currNum);
var number = id.Item4 + 1;
var c3 = id.Item3;
var c2 = id.Item2;
var c1 = id.Item1;
if (number > 999)
{
number = 0;
c3++;
if (c3 > 'Z')
{
c3 = 'A';
c2++;
if (c2 > 'Z')
{
c2 = 'A';
c1++;
if (c1 > 'Z')
{
throw new IndexOutOfRangeException("Next ID bigger than \"ZZZ999\"");
}
}
}
}
var next = new Tuple<char, char, char, int>(c1, c2, c3, number);
return $"{next.Item1}{next.Item2}{next.Item3}{next.Item4.ToString().PadLeft(3, '0')}";
}
}
不是选择和递增字符串,而是生成一个带有 SEQUENCE 或 IDENTITY 列的 ID,如果必须,在客户端将其转换为字符串。 EG
static string GetId(int id)
{
var chars = new char[6];
char Zero = '0';
chars[5] = (char) (Zero + id % 10);
id = id / 10;
chars[4] = (char)(Zero + id % 10);
id = id / 10;
chars[3] = (char)(Zero + id % 10);
id = id / 10;
char A = 'A';
chars[2] = (char)(A + id % 26);
id = id / 26;
chars[1] = (char)(A + id % 26);
id = id / 26;
chars[0] = (char)(A + id % 26);
return new string(chars);
}
要获取序列值,请像这样向 DbContext 添加一个方法:
private long GetSequenceNextVal(string sequenceName)
{
var con = Database.GetDbConnection();
var initialState = con.State;
if (con.State != System.Data.ConnectionState.Open)
con.Open(); //the DbContext will clean up after exceptions
var cmd = con.CreateCommand();
var connectionType = con.GetType().Name;
string sql;
if (connectionType == "SqlConnection")
{
sql = $"select next value for {sequenceName};";
}
else if (connectionType == "NpgsqlConnection")
{
sql = $"SELECT nextval('{sequenceName}');";
}
else if (connectionType == "OracleConnection")
{
sql = $"select {sequenceName}.nextval from dual";
}
else
{
throw new NotImplementedException($"No sequence generatator for {connectionType}");
}
cmd.CommandText = sql;
var result = cmd.ExecuteScalar();
var val = Convert.ToInt64(result);
if (initialState == System.Data.ConnectionState.Closed)
con.Close();
return val;
}