I want to add 1
to a value within my database within a transaction. I want to ensure that the record is updated properly and hasn't been changed by someone else during that time.
I have the following code that I thought would work, but I can still pause during debugging, change the record in the database to something different and then it becomes inconsistent.
Here's my code:
using (var transaction = this.Context.Database.BeginTransaction())
{
try
{
if (quiz.PasswordRequiredToTakeQuiz())
{
// Check password exists for quiz
bool passwordIsValid = quiz.QuizPasswords.Any(x => x.Password.ToLower() == model.QuizPassword.ToLower() && !x.Deleted);
QuizPassword quizPassword = quiz.QuizPasswords.Where(x => x.Password.ToLower() == model.QuizPassword.ToLower() && !x.Deleted).First();
string passwordError = "Sorry the password you provided has expired or is not valid for this quiz";
if (!passwordIsValid)
{
ViewData.ModelState.AddModelError("QuizPassword", passwordError);
}
else
{
// Password is valid for use with this quiz, but can it be used?
if (quizPassword.RemainingUses < 1 && quizPassword.UnlimitedUses != true)
{
// Password cannot be used
ViewData.ModelState.AddModelError("QuizPassword", passwordError);
}
else
{
// Password CAN be used
if (!quizPassword.UnlimitedUses)
{
quizPassword.RemainingUses--;
}
// Increase use count
quizPassword.UseCount++;
this.Context.EntitySet<QuizPassword>().Attach(quizPassword);
this.Context.Entry(quizPassword).State = EntityState.Modified;
// I can change the record UseCount value in the database at this point
// then when it saves, it becomes inconsistent with other's use of
// the password
this.Context.SaveChanges();
}
}
}
// Commit the changes
transaction.Commit();
}
catch(Exception)
{
transaction.Rollback();
}
finally
{
transaction.Dispose();
}
}
Turn of events:
- Initially, UseCount =
0
in the database - I run the code up to just before
SaveChanges()
- I go into the database and change UseCount to
5
- I allow SaveChanges() to be called (Shouldn't be possible without being blocked)
- UseCount value in the database will be
1
.
Normally I'd achieve this using SELECT FOR UPDATE
to lock the record temporarily, but I was originally using PHP + MySQL.
I have read that the lock isn't possible, so I'm wondering how this can be achieved.
It's important because I don't want people to be able to use a password more than a set number of times! If it is possible for someone to change the value in the mean time, it doesn't guarantee the correct number of uses.