You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET MVC中IActionResult的Lock/Monitor实现问题

ASP.NET MVC 实现概念编辑的独占锁定方案

核心思路

基于本地数据库实现编辑锁(进程内的Lock/Monitor仅适用于单进程场景,无法跨用户/进程控制,因此用数据库字段记录锁定状态更可靠),通过在概念表中添加锁定相关字段,结合用户标识和过期时间,实现同一概念仅允许单个用户编辑的控制逻辑。

步骤1:修改数据库表

在你的Concept表中添加锁定状态相关字段:

ALTER TABLE Concept
ADD IsLocked BIT NOT NULL DEFAULT 0,
    LockedBy NVARCHAR(128) NULL, -- 存储用户ID(如ASP.NET Identity的UserId)或用户名
    LockExpiration DATETIME NULL; -- 锁的过期时间,防止用户异常退出导致死锁

步骤2:仓储层实现锁定逻辑

在仓储接口(如IConceptRepository)中定义锁定相关方法:

public interface IConceptRepository
{
    // 尝试锁定概念,返回是否成功
    bool TryLockConcept(int conceptId, string userId);
    // 释放指定用户持有的概念锁
    void ReleaseLock(int conceptId, string userId);
    // 检查概念的锁定状态
    (bool IsLocked, string LockedBy, DateTime? LockExpiration) CheckLockStatus(int conceptId);
    // 刷新锁的过期时间(用户操作时延长锁定时长)
    bool RefreshLock(int conceptId, string userId);
}

以下是EF Core实现示例:

public class ConceptRepository : IConceptRepository
{
    private readonly YourDbContext _context;
    private readonly TimeSpan _lockDuration = TimeSpan.FromMinutes(15); // 锁定有效期15分钟

    public ConceptRepository(YourDbContext context)
    {
        _context = context;
    }

    public bool TryLockConcept(int conceptId, string userId)
    {
        var concept = _context.Concepts.Find(conceptId);
        if (concept == null) return false;

        // 未锁定或锁已过期,直接加锁;如果是当前用户持有的锁,自动刷新时间
        if (!concept.IsLocked || concept.LockExpiration <= DateTime.Now)
        {
            concept.IsLocked = true;
            concept.LockedBy = userId;
            concept.LockExpiration = DateTime.Now.Add(_lockDuration);
            _context.SaveChanges();
            return true;
        }
        else if (concept.LockedBy == userId)
        {
            RefreshLock(conceptId, userId);
            return true;
        }
        return false;
    }

    public void ReleaseLock(int conceptId, string userId)
    {
        var concept = _context.Concepts.Find(conceptId);
        if (concept != null && concept.LockedBy == userId)
        {
            concept.IsLocked = false;
            concept.LockedBy = null;
            concept.LockExpiration = null;
            _context.SaveChanges();
        }
    }

    public (bool IsLocked, string LockedBy, DateTime? LockExpiration) CheckLockStatus(int conceptId)
    {
        var concept = _context.Concepts.Find(conceptId);
        if (concept == null)
            return (false, null, null);
        
        // 锁已过期则自动解锁
        if (concept.IsLocked && concept.LockExpiration <= DateTime.Now)
        {
            concept.IsLocked = false;
            concept.LockedBy = null;
            concept.LockExpiration = null;
            _context.SaveChanges();
            return (false, null, null);
        }
        return (concept.IsLocked, concept.LockedBy, concept.LockExpiration);
    }

    public bool RefreshLock(int conceptId, string userId)
    {
        var concept = _context.Concepts.Find(conceptId);
        if (concept != null && concept.LockedBy == userId)
        {
            concept.LockExpiration = DateTime.Now.Add(_lockDuration);
            _context.SaveChanges();
            return true;
        }
        return false;
    }
}

步骤3:Controller层处理编辑请求

GET 请求(进入编辑页面)

public IActionResult EditConcept(int id)
{
    var userId = User.Identity.Name; // 或用User.FindFirstValue(ClaimTypes.NameIdentifier)获取用户ID
    var lockStatus = _conceptRepository.CheckLockStatus(id);
    
    // 被其他用户锁定,返回提示页
    if (lockStatus.IsLocked && lockStatus.LockedBy != userId)
    {
        ViewBag.LockMessage = $"该概念已被用户 {lockStatus.LockedBy} 锁定,预计 {lockStatus.LockExpiration?.ToString("yyyy-MM-dd HH:mm")} 后自动解锁";
        return View("LockError");
    }

    // 尝试锁定概念,失败则返回提示
    var lockSuccess = _conceptRepository.TryLockConcept(id, userId);
    if (!lockSuccess)
    {
        return View("LockError");
    }

    // 获取概念数据并返回编辑视图
    var concept = _conceptRepository.GetById(id);
    return View(concept);
}

POST 请求(提交编辑内容)

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult EditConcept(ConceptViewModel model)
{
    var userId = User.Identity.Name;
    if (!ModelState.IsValid)
    {
        // 验证失败,返回编辑页并保留锁
        return View(model);
    }

    try
    {
        // 执行编辑逻辑
        _conceptRepository.Update(model);
        _context.SaveChanges();
        // 编辑成功后释放锁
        _conceptRepository.ReleaseLock(model.Id, userId);
        return RedirectToAction("Index");
    }
    catch (Exception ex)
    {
        ModelState.AddModelError("", $"编辑失败:{ex.Message}");
        // 编辑失败,保留锁让用户继续操作
        return View(model);
    }
}

可选优化:前端自动刷新锁

为避免用户长时间操作导致锁过期,可在编辑页添加定时AJAX请求刷新锁:

// 每10分钟刷新一次锁
setInterval(function() {
    $.ajax({
        url: '@Url.Action("RefreshLock", "Concept")',
        type: 'POST',
        data: { conceptId: @Model.Id },
        success: function(response) {
            if (!response.success) {
                alert("你的编辑锁已过期,请重新进入编辑页面");
                window.location.href = '@Url.Action("Index", "Concept")';
            }
        }
    });
}, 600000); // 10分钟=600000毫秒

对应的Controller方法:

[HttpPost]
public IActionResult RefreshLock(int conceptId)
{
    var userId = User.Identity.Name;
    var success = _conceptRepository.RefreshLock(conceptId, userId);
    return Json(new { success });
}

注意事项

  • 不要使用进程内的Lock/Monitor:ASP.NET MVC多进程环境下,进程内锁无法跨进程控制用户并发编辑。
  • 合理设置过期时间:根据业务场景调整(如15-30分钟),避免用户临时离开导致锁长时间占用。
  • 异常场景处理:用户关闭浏览器、崩溃等情况,依赖过期时间自动解锁,无需额外处理。

内容的提问来源于stack exchange,提问作者gailtye

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 14:55:40