2个用户正在执行删除操作时条件失败

时间:2017-08-15 13:25:52

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

我的问题非常简单,我有一个页面,管理员可以管理授权管理员列表但我的问题是数据库中应该总是至少有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个人将能够访问该页面。时间(它可能在更糟糕的情况下工作,但我希望有更好的解决方案)

1 个答案:

答案 0 :(得分:1)

如果您愿意使用存储过程,则可以使用sp_getapplock。这类似于存储过程中的互斥锁。因此,在受保护的代码部分,您可以执行记录计数并删除,因为在释放锁之前,对该过程的另一次调用将不会执行该代码。

我添加了一个示例过程,忽略了设置@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