我希望在事务中将1添加到数据库中的值。我想确保记录是正确更新的,并且在这段时间内没有被其他人更改。
我有下面的代码,我认为这是可行的,但我仍然可以在调试期间暂停,将数据库中的记录更改为不同的内容,然后它就变得不一致了。
这是我的密码:
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();
}
}事态转变:
0SaveChanges()之前运行代码51。通常情况下,我会使用SELECT FOR UPDATE暂时锁定记录,但我最初使用的是PHP + MySQL。
我读过这个锁是不可能的,所以我想知道这是如何实现的。
这很重要,因为我不希望人们使用密码超过设定的次数!如果有人可以在同一时间内更改值,则不能保证正确的使用次数。
发布于 2014-10-09 14:44:37
我创建了一个存储过程,它返回我想返回的值的SELECT语句,这是一个Success int。
DROP PROCEDURE IF EXISTS UsePassword;
DELIMITER //
CREATE PROCEDURE UsePassword (QuizId INT(11), PasswordText VARCHAR(25))
BEGIN
/* Get current state of password */
SELECT RemainingUses, UnlimitedUses, Deleted INTO @RemainingUses, @UnlimitedUses, @Deleted FROM QuizPassword q WHERE q.QuizId = QuizId AND `Password` = PasswordText AND Deleted = 0 LIMIT 0,1 FOR UPDATE;
IF FOUND_ROWS() = 0 OR @Deleted = 1 THEN
/* Valid password not found for quiz */
SET @Success = 0;
ELSEIF @UnlimitedUses = 1 THEN
UPDATE QuizPassword SET UseCount = UseCount + 1 WHERE QuizId = QuizId AND `Password` = PasswordText;
SET @Success = ROW_COUNT();
ELSEIF @RemainingUses > 0 AND @UnlimitedUses = 0 THEN
UPDATE QuizPassword SET UseCount = UseCount + 1, RemainingUses = RemainingUses - 1 WHERE QuizId = QuizId AND `Password` = PasswordText;
SET @Success = ROW_COUNT();
ELSE
SET @Success = 0;
END IF;
/* Return rows changed rows */
SELECT @Success AS Success;
END //
DELIMITER;我不得不创建一个新的对象来保存这些值,我在里面只有一个字段,但是你可以放更多。
// Class to hold return values from stored procedure
public class UsePasswordResult
{
public int Success { get; set; }
// could have more fields...
}我将最后的代码简化为这样,它调用存储过程并将值赋值给对象中的成员变量:
using (var transaction = this.Context.Database.BeginTransaction())
{
try
{
if (quiz.PasswordRequiredToTakeQuiz())
{
// Attempt to use password
UsePasswordResult result = this.Context.Database.SqlQuery<UsePasswordResult>("CALL UsePassword({0}, {1})", quiz.Id, model.QuizPassword).FirstOrDefault();
// Check the result of the password use
if (result.Success != 1)
{
// Failed to use the password
ViewData.ModelState.AddModelError("QuizPassword", "Sorry the password you provided has expired or is not valid for this quiz");
}
}
// Is model state still valid after password checks?
if (ModelState.IsValid)
{
// Do stuff
}
transaction.Commit();
}
catch(Exception)
{
transaction.Rollback();
}
finally
{
transaction.Dispose();
}
}从存储过程返回的值必须与作为结果生成的类中的名称完全相同。
因为我在事务using语句中调用using,所以语句锁定记录,因为我选择它为SELECT... FOR UPDATE,直到在代码中调用transaction.Commit()/Rollback()/Dispose() .因此,防止任何人试图使用密码,而其他任何人。
发布于 2014-10-09 10:00:11
一种解决方案是使用普通sql (ado.net)和悲观锁定。
BEGIN TRANSACTION
SELECT usecount, unlimiteduses FROM quizpassword WITH (UPDLOCK, HOLDLOCK) WHERE id = x;
// check usecount here
// only do this if unlimitedUses == false
UPDATE quizpassword SET usecount = usecount + 1 WHERE id = x;
UPDATE quizpassword SET remaininguses = remaininguses -1 WHERE id = x;
COMMIT TRANSACTION // (lock is released)https://stackoverflow.com/questions/26275199
复制相似问题