我有以下代码:
var strategy = _dbContext.Database.CreateExecutionStrategy();
return await strategy.ExecuteAsync(async () =>
{
using (var transaction = _dbContext.Database.BeginTransaction(IsolationLevel.Serializable))
{
await (new Validator(_dbContext).ValidateRequestAndThrowAsync(request));
var newUser = new Models.User
{
Name = request.Name,
Surname = request.Surname,
Username = request.Username,
};
_dbContext.Users.Add(newUser);
await _dbContext.SaveChangesAsync();
transaction.Commit();
return newUser;
}
});
其中_dbContext
是MySQL数据库上下文,
await (new Validator(_dbContext).ValidateRequestAndThrowAsync(request));
使用相同的Username
检查数据库中是否已使用_dbContext
。
这只是用于内部测试的代码。我知道您可以在数据库级别检查此约束。我正在尝试模拟的是检查数据库中不可放置的约束。因此,出于这个问题的目的,请考虑通过唯一索引检查Username
的唯一性是不可行的。
如果我调用此函数10次或100次,则其中99.9%的时间将按预期执行:只有一个请求通过,其余的被停止,但Validator
发送了一个很好的异常。 >
如果我调用它1000次,则每次运行我将开始可靠地获得2-5次重复。即使读取请求的日志,似乎只有1个请求通过,但我却插入了2-5个重复的行。
我在做什么错?为什么每1000个请求中有2-5个重复?
在各种尝试中,我还使用以下方法设置了数据库上下文:
services.AddDbContext<MainContext>(
options => options.UseMySql(connectionString,
mySqlOptions =>
{
mySqlOptions.EnableRetryOnFailure(
maxRetryCount: 10,
maxRetryDelay: TimeSpan.FromSeconds(3),
errorNumbersToAdd: new int[]
{
1205, // Deadlock, see: https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/blob/ddf8b77e6f58f3915dd170ea1de73755a7b3cae6/upstream/EFCore.Upstream/Storage/Internal/MySqlTransientExceptionDetector.cs#L95
});
mySqlOptions.ServerVersion(new Version(5, 6, 41), ServerType.MySql);
}
));
但是,鉴于所有请求在自身上使用都会失败,并显示一条消息,提示应改为使用CreateExecutionStrategy
。
此处要求的是ValidateRequestAndThrowAsync
:
public static async Task ValidateRequestAndThrowAsync<T>(this IValidator<T> validator, T request)
{
var validationResult = await validator.ValidateAsync(request);
if (validationResult.IsValid)
return;
throw new CustomError(validationResult.Errors);
}
验证者:
private class Validator : AbstractValidator<Request>
{
public Validator(MainContext dbContext)
{
RuleFor(x => x.Name)
.NotEmpty();
RuleFor(x => x.Surname)
.NotEmpty();
RuleFor(x => x.Username)
.NotEmpty()
.SetValidator(new UniqueUsernameValidator(dbContext));
}
}
public class UniqueUsernameValidator : AbstractValidator<string>
{
private readonly MainContext _dbContext;
private readonly int? _userId;
public UniqueUsernameValidator(MainContext dbContext, int? userId = null)
{
_dbContext = dbContext;
_userId = userId;
RuleFor(x => x)
.MustAsync(NotExists);
}
private async Task<bool> NotExists(string username, CancellationToken cancellationToken)
{
return !await _dbContext.Users
.Where(x => x.Username == username)
.Where(x => !_userId.HasValue || x.Id != _userId.Value)
.AnyAsync(cancellationToken);
}
}