C# MVC中的锁定实体 简短问题
在MVC项目中,如何锁定我的实体,以便每次只能对其执行一个操作 长问题 我有一个MVC项目,我希望我的操作方法是C# MVC中的锁定实体 简短问题,c#,asp.net-mvc,C#,Asp.net Mvc,在MVC项目中,如何锁定我的实体,以便每次只能对其执行一个操作 长问题 我有一个MVC项目,我希望我的操作方法是[SessionState(SessionStateBehavior.ReadOnly)]。但在执行此操作时,用户甚至可以在一个长时间运行的操作方法尚未完成之前执行另一个操作方法。因为我有很多计算,动作方法必须按照预定义的顺序执行,所以在一个动作方法结束之前执行另一个动作方法会产生很多问题。举个例子,我有一个名为Report的主实体,我必须以某种方式确保一个报表一次只能由一个用户执行一
[SessionState(SessionStateBehavior.ReadOnly)]
。但在执行此操作时,用户甚至可以在一个长时间运行的操作方法尚未完成之前执行另一个操作方法。因为我有很多计算,动作方法必须按照预定义的顺序执行,所以在一个动作方法结束之前执行另一个动作方法会产生很多问题。举个例子,我有一个名为Report
的主实体,我必须以某种方式确保一个报表一次只能由一个用户执行一个操作。因此,我必须锁定我的报告
。即使我不使用[SessionState(SessionStateBehavior.ReadOnly)]
我也必须锁定报表,以便多个用户不会同时出于其他特定原因编辑相同的报表。目前,我正在将这些信息写入数据库,大致如下:
ReportId
LockedUserId
IsInPorcess
每次操作开始前,我必须将IsInProcess
设置为true
,并在操作完成后将其重置为false
。因为我有很多动作方法,所以我创建了ActionFilter
如下:
public class ManageReportLockAttribute
: FilterAttribute, IActionFilter
{
public ManageReportLockAttribute()
{
}
public void OnActionExecuting(ActionExecutingContext filterContext)
{
...
ReportLockInfo lockInfo = GetFromDatabase(reportId);
if(lockInfo.IsInProcess)
RedirectToInformationView();
lockInfo.IsInProcess = true;
SaveToDatabase(lockInfo);
...
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
...
ReportLockInfo lockInfo = GetFromDatabase(reportId);
lockInfo.IsInProcess = false;
SaveToDatabase(lockInfo);
...
}
}
在大多数情况下,它都可以工作,但是它有一些奇怪的问题(更多信息,请参阅)。
我的问题是“我如何通过不同的更可接受的方式实现相同的功能(锁定报告)”。
我觉得在使用多线程时,它类似于锁定,但在IMO中并不完全相同
很抱歉问了这么长、这么宽、这么尴尬的问题,但我想要一个方向。提前感谢。尽管
OnActionExecuting
按预期运行,但未调用OnActionExecuted
的一个原因是OnActionExecuting
中存在未处理的异常。特别是在处理数据库时,有各种原因可能导致异常,例如:
- User1启动进程并锁定实体
- User2还希望在User1保存更改之前启动该过程。因此,
的检查不会导致重定向,User2也希望保存锁。在这种情况下,应发生并发冲突,因为User1同时保存了实体李>IsInProcess
IsInProcess
,S是SaveChanges
):第一个好例子:
User1 CS
User2 CS (check is done after save, no problem)
现在是一个糟糕的案例:
User1 CS
User2 CS (check takes place after check for User1, but before SaveChanges becomes effective ==> concurrency violation)
如示例所示,确保只有一个用户可以放置锁是至关重要的。有几种方法可以解决这个问题。在所有情况下,确保OnActionExecuting
中出现异常的原因尽可能少。处理并记录异常
请注意,所有同步方法都会对应用程序的性能产生负面影响因此,如果您还没有考虑是否可以通过重组操作或数据模型来避免锁定报表,那么这将是第一件事。
简单方法:线程同步
一种简单的方法是使用线程同步。只有当应用程序在单个进程中运行,而不是在web场/云中运行时,这种方法才有效。如果应用程序将在以后某个时间点安装在服务器场中,您需要决定是否能够更改该应用程序。此示例显示了一种简单的方法(使用静态对象进行锁定):
有关使用lock
的详细信息,请参见此。如果您需要更多的控制,请使用C#查看。命名互斥锁是一种替代方法,它以更精细的粗化方式提供锁定
如果要锁定reportId
而不是静态对象,则需要对相同的reportId
使用相同的锁定对象。字典可以存储锁对象:
private static readonly IDictionary<int, object> lockObjectsByReportId = new Dictionary<int, object>();
private static object GetLockObjectByReportId(int reportId)
{
int lockObjByReportId;
if (lockObjectsByReportId.TryGetValue(reportId, out lockObjByReportId))
return lockObjByReportId;
lock(lockObj) // use global lock for a short operation
{
if (lockObjectsByReportId.TryGetValue(reportId, out lockObjByReportId))
return lockObjByReportId;
lockObjByReportId = new object();
lockObjectsByReportId.Add(reportId, lockObjByReportId);
return lockObjByReportId;
}
}
数据库方法:事务和隔离级别
另一种处理方法是使用数据库事务和隔离级别。这种方法也适用于多服务器环境。在本例中,您不会使用实体框架来访问数据库,而是将代码移动到数据库服务器上运行的存储过程中。通过在事务中运行存储过程并选择正确的隔离级别,可以避免用户在另一个用户更改数据时读取数据
这显示了SQL Server隔离级别的概述 查看原子操作:通常会有某种形式的单例,它使用原子操作和/或锁。。。在数据库中这样做可能不正确…@Milney,谢谢。请您帮助我了解,如果我有多个具有不同ID的报告,我如何通过
Interlocked
类锁定它们?在我看来,将数据库中的实体标记为已锁定正是正确的方法。考虑在一个服务器场中使用多个webfrontend服务器。线程同步在这种情况下没有帮助。为了确保应用程序能够很好地扩展(即使现在不需要),您需要在数据库级别锁定。数据库在所有webfrontend服务器之间共享。但是,如果出现任何问题,您需要提供一种机制来手动解锁实体。@Markus,谢谢提供信息。但是这种应用被大量使用,并且经常发生。有关此问题的更多信息,请参阅链接问题。现在,我只有一个应用程序,那么至少现在有更好的选择吗?@Markus——我想说,使用数据库来实现这一点需要太多的延迟——如果你需要分布式解决方案,你必须使用像Redis这样的缓存层。Redis支持交易:谢谢你的回答。实际上,我认为在执行操作时不会发生异常。因为,没有任何迹象
private static readonly IDictionary<int, object> lockObjectsByReportId = new Dictionary<int, object>();
private static object GetLockObjectByReportId(int reportId)
{
int lockObjByReportId;
if (lockObjectsByReportId.TryGetValue(reportId, out lockObjByReportId))
return lockObjByReportId;
lock(lockObj) // use global lock for a short operation
{
if (lockObjectsByReportId.TryGetValue(reportId, out lockObjByReportId))
return lockObjByReportId;
lockObjByReportId = new object();
lockObjectsByReportId.Add(reportId, lockObjByReportId);
return lockObjByReportId;
}
}
// ...
lock(GetLockObjectByReportId(reportId))
{
// ...
}