【问题标题】:Conditions fails when 2 user are doing the delete action当 2 个用户正在执行删除操作时条件失败
【发布时间】:2017-08-15 13:25:51
【问题描述】:

我的问题很简单,我有一个页面,管理员可以在其中管理授权管理员列表,但我的问题是数据库中应该始终至少有 1 个管理员,所以我写了这个简单的if 来检查删除前数据库中的管理员数量应大于 1

这是我的控制器删除操作

 public ActionResult DeleteAdmin(int idAdmin)
    {

        using (InscriptionFormationEntities dbm = new InscriptionFormationEntities())
        {
            Administrateurs admin = dbm.Administrateurs.FirstOrDefault(x => x.id == idAdmin);
            if(admin.NomLogin == Session["utilisateur"].ToString())
            {
                ModelState.AddModelError("Current User", "You can't delete Yourself");
            }
            if(dbm.Administrateurs.ToList().Count <= 1)
            {
                ModelState.AddModelError("LastAdmin", "At least 1 admin must be left");
            }
            if (ModelState.IsValid)
            {
                dbm.Administrateurs.Remove(admin);
                dbm.SaveChanges();
            }
            List<Administrateurs> ListeAdmin = dbm.Administrateurs.ToList();
            return RedirectToAction("Utilisateurs", "Admin");
        }
    }

此代码在一种情况下工作得非常好:如果剩下 2 个管理员并且他们尝试同时删除彼此,他们将能够这样做。我试图改变我放置条件的位置(例如在删除之前),但条件仍然总是返回 false。所以我想知道是否有办法防止这种情况发生,比如定义一次只能删除一个实例或类似的东西。

编辑

我调查了这个问题: How can I lock a table on read, using Entity Framework?

但这并不能解决我的问题,因为我不想锁定读取,而是锁定删除,因为如果我锁定只读 1 人一次将能够访问该页面(它可以在更糟糕的情况下工作,但我希望有更好的解决方案)

【问题讨论】:

标签: c# asp.net-mvc entity-framework asp.net-mvc-5


【解决方案1】:

如果您愿意使用存储过程,您可以使用 sp_getapplock。这就像存储过程中的互斥锁一样工作。因此,在受保护的代码部分,您可以进行记录计数并删除,因为知道对该过程的另一个调用将不会执行该代码,直到锁被释放。

我已经添加了一个示例程序,忽略 set @msg 和 raiserror 语句,这些只是为了显示状态,重要的位从 Exec 语句开始。

在两个单独的选项卡中运行这些过程,它们都将启动,但第二个将等待 15 秒以获取锁,然后如果获得锁则继续。

exec critical_rhz '00:00:20' 模拟一个需要 20 秒的进程

CREATE PROC dbo.critical_rhz  @wait_duration varchar(30) = '00:01:00' -- default one minute
/* Performs a task in a critical section of code that can only be run
   by one session or transaction at a time. 
   The task is simulated by a WAIT 
   The raiseerror() with nowait is just for real time status displays */
AS
declare @rc int = 0 -- return code
  , @msg varchar(2000)
set @msg = convert(varchar,getdate(), 114) + ' critical_section_worker starting'
raiserror (@msg, 0, 1) with nowait 
Begin Try
Begin tran
set @msg= convert(varchar,getdate(), 114) + ' requesting lock'
raiserror (@msg, 0, 1) with nowait
Exec @rc = sp_getapplock @Resource='CriticalSectionWorker' -- the resource to be locked
     , @LockMode='Exclusive'  -- Type of lock
     , @LockOwner='Transaction' -- Transaction or Session
     , @LockTimeout = 15000 -- timeout in milliseconds, 15 seconds
set @msg= convert(varchar,getdate(), 114) + ' sp_getapplock returned ' + convert(varchar(30), @rc) + ' -- '
  + case when @rc < 0 then 'Could not obtain the lock'  else 'Lock obtained'  end
raiserror (@msg, 0, 1) with nowait
 if @rc >= 0 begin
  set @msg= convert(varchar,getdate(), 114) + ' got lock starting critical work '
  raiserror (@msg, 0, 1) with nowait
waitfor delay @wait_duration -- Critical Work simulated by waiting
  commit tran -- will release the lock
  set @msg= convert(varchar,getdate(), 114) + ' work complete released lock' 
  raiserror (@msg, 0, 1) with nowait
  end 
 else begin
  rollback tran
  set @rc = 50000
end
end try
begin catch
 set @msg = 'ERROR: ' + ERROR_MESSAGE() + ' at ' 
        + coalesce(ERROR_PROCEDURE(), '')
        + coalesce (' line:' + convert(varchar(30), ERROR_LINE()), '')
 RAISERROR (@msg, 0, 1) with nowait -- ensure the message gets out        
 if @@Trancount > 1 rollback tran
raiserror (@msg, 16, 1)
end catch
return @rc
GO

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-28
    • 1970-01-01
    相关资源
    最近更新 更多