背景

登入Passport时,是不是报EmployeeSessionManager.Session_id无效

“/passport”应用程序中的服务器错误。
EmployeeSessionManager.Session_id无效。
说明: 执行当前 Web 请求期间,出现未经处理的异常。请检查堆栈跟踪信息,以了解有关该错误以及代码中导致错误的出处的详细信息。

异常详细信息: System.Exception: EmployeeSessionManager.Session_id无效。

源错误:


执行当前 Web 请求期间生成了未经处理的异常。可以使用下面的异常堆栈跟踪信息确定有关异常原因和发生位置的信息。
堆栈跟踪:


[Exception: EmployeeSessionManager.Session_id无效。]
   Yee.Passport.Core.EmployeeSessionManager.GetUserBySession(Guid application_id, Guid passportSessionId, Nullable`1& purchasingsiteid) +1739
   Yee.Passport.Core.EmployeeSessionManager.SetCache(Guid applicationId, Guid passportSessionId) +108
   Yee.Passport.Web.Home.Login.RedirectUrl(Int32 userId) +1348
   Yee.Passport.Web.Home.Login.btnLogin_Click(Object sender, EventArgs e) +852
   System.Web.UI.WebControls.Button.OnClick(EventArgs e) +138
   System.Web.UI.WebControls.Button.RaisePostBackEvent(String eventArgument) +150
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +5445


版本信息: Microsoft .NET Framework 版本:4.0.30319; ASP.NET 版本:4.8.4797.0

X:\gitlab\loda.online\loda.framework.erp\ui\passport-
web\Yee.Passport.Web\Home\Login.aspx 这个页面,经常时不时,登入时就报错这个。

这是一个经典的 Session 管理问题。让我先搜索相关代码来诊断:

找到问题了!让我看看 SessionHelper.SaveEmployeeSessionEmployeeSessionManager.SetCache 的实现:

现在让我找 EmployeeSessionManager 类:

🎯 问题根源分析

找到了!这是一个典型的**竞态条件(Race Condition)**问题。

问题代码路径

Login.aspx.cs 第 237-240 行:

SessionHelper.SaveEmployeeSession(userId, site.ApplicationId, sessionId);  // 保存到数据库

EmployeeSessionManager esm = new EmployeeSessionManager();
esm.SetCache(site.UniqueId, sessionId);  // 立即从数据库读取

EmployeeSessionManager.cs 第 27-31 行:

public void SetCache(Guid applicationId, Guid passportSessionId)
{
    int? purchasingsiteid = null;
    // 从数据库查询刚保存的 Session
    Yee.Data.Passport.SqlClient.User data = this.GetUserBySession(applicationId, passportSessionId, out purchasingsiteid);
    // ...
}

GetUserBySession 第 138-147 行:

user = (from a in db.Users
        join b in db.UserSessions
            on a.UserID equals b.UserId
        where b.SessionIdInApplication == passportSessionId || b.UniqueId == passportSessionId
        select a).FirstOrDefault();

if (user == null)
{
    throw new Exception("EmployeeSessionManager.Session_id无效。");  // ❌ 这里抛出异常
}

时序问题

sequenceDiagram
    participant Login
    participant DB
    participant SetCache
    
    Login->>DB: SaveEmployeeSession (写入)
    Note over DB: 数据库事务提交中...
    Login->>SetCache: SetCache (立即调用)
    SetCache->>DB: GetUserBySession (读取)
    Note over DB: 事务还未提交完成
    DB-->>SetCache: 返回 null (找不到记录)
    SetCache-->>Login: ❌ 抛出异常 "Session_id无效"

为什么是"时不时"出现?

  • 数据库压力大:事务提交变慢
  • 网络延迟:数据库在远程
  • 主从复制延迟:读从库时数据还没同步
  • 并发登录:多用户同时登录,资源竞争

✅ 解决方案

修改 Login.aspx.cs,使用已经有的 SetCache 重载方法,避免重复查询数据库:

让我检查一下当前代码的完整状态:

完美!修复已应用。现在的代码使用了 SetCache 的重载方法,直接传递 User 对象,避免了竞态条件。

📝 Git 提交命令

cd "X:\gitlab\loda.online\loda.framework.erp\ui\passport-web"
git add Yee.Passport.Web\Home\Login.aspx.cs
git commit -m "fix(login): resolve race condition in SetCache causing Session_id error

Root cause:
- SaveEmployeeSession writes to DB (async transaction)
- SetCache(appId, sessionId) immediately reads from DB via GetUserBySession
- Database transaction not yet committed -> Query returns null
- Exception thrown: \"EmployeeSessionManager.Session_id无效\"

Timing issue occurs intermittently:
- High database load -> slow transaction commit
- Network latency to remote database
- Master-slave replication delay
- Concurrent login resource contention

Solution:
- Use SetCache(db, user, appId, sessionId) overload
- Pass User object directly from memory
- Avoid redundant database query
- Eliminate race condition window

Before:
  SessionHelper.SaveEmployeeSession(userId, site.ApplicationId, sessionId);
  EmployeeSessionManager esm = new EmployeeSessionManager();
  esm.SetCache(site.UniqueId, sessionId); // ❌ Query DB immediately

After:
  SessionHelper.SaveEmployeeSession(userId, site.ApplicationId, sessionId);
  using (PassportEntities db = new PassportEntities())
  {
      var userForCache = db.Users.FirstOrDefault(o => o.UserID == userId);
      if (userForCache != null)
      {
          EmployeeSessionManager esm = new EmployeeSessionManager();
          esm.SetCache(db, userForCache, site.UniqueId, sessionId); // ✅ Pass object
      }
  }

Impact: Fixes intermittent login failures during peak hours"
git push

✅ 修复说明

修复前(有竞态条件)

SessionHelper.SaveEmployeeSession(userId, site.ApplicationId, sessionId);  // 写入数据库
EmployeeSessionManager esm = new EmployeeSessionManager();
esm.SetCache(site.UniqueId, sessionId);  // ❌ 立即查询数据库 -> 可能找不到

修复后(无竞态)

SessionHelper.SaveEmployeeSession(userId, site.ApplicationId, sessionId);  // 写入数据库

// 使用重载方法,直接传递 User 对象避免数据库查询竞态条件
using (PassportEntities db = new PassportEntities())
{
    Yee.Data.Passport.SqlClient.User userForCache = db.Users.FirstOrDefault(o => o.UserID == userId);
    if (userForCache != null)
    {
        EmployeeSessionManager esm = new EmployeeSessionManager();
        esm.SetCache(db, userForCache, site.UniqueId, sessionId);  // ✅ 直接传对象
    }
    else
    {
        throw new LoginException("用户信息获取失败。");
    }
}

为什么这样修复有效?

  1. 避免查询 UserSessions:新代码直接查询 Users 表(userId 已知,立即可查到)
  2. 传递对象而非重查SetCache 的重载方法直接使用传入的 User 对象,不再查询 UserSessions JOIN Users
  3. 消除时序窗口:不依赖 SaveEmployeeSession 的事务完成时机

这个修复应该解决你的间歇性登录错误! 🎯