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

NHibernate问题:Session缓存与查询结果为何不一致?

NHibernate FlushMode.Commit下查询结果不一致问题解析

问题描述

我们使用NHibernate v4.0.30319,设置FlushMode.Commit。以数据库中含IsActive列的Employee表为例:
在某事务中,通过代码获取empId=1的Employee实体并将其IsActive设为false,此时未提交事务也未执行Flush操作。紧接着执行查询获取所有IsActive=true的员工,结果中仍包含该员工且IsActive值为false。
我无法理解此不一致行为:若查询针对数据库,应返回该员工IsActive为true;若基于一级缓存,则不应包含该员工。为何会出现此现象?


复现环境与代码

Employee表结构

Employee表结构

NHibernate配置

if (_sessionFactory == null)
{
    var configuration = new Configuration();

    // Set connection properties for SQL Server
    configuration.SetProperty(NHibernate.Cfg.Environment.ConnectionDriver, typeof(SqlClientDriver).AssemblyQualifiedName);
    configuration.SetProperty(NHibernate.Cfg.Environment.ConnectionString, "Data Source=INLT-D72FCSG3\\SQLEXPRESS;Initial Catalog=TestDB;Integrated Security=True;");
    configuration.SetProperty(NHibernate.Cfg.Environment.Dialect, typeof(MsSql2012Dialect).AssemblyQualifiedName);
    configuration.SetProperty(NHibernate.Cfg.Environment.ShowSql, "true");
    configuration.SetProperty(NHibernate.Cfg.Environment.FormatSql, "true");
    configuration.SetProperty(NHibernate.Cfg.Environment.Hbm2ddlAuto, "update");


    configuration.AddFile("Employee.hbm.xml"); // Add your mapping files
    _sessionFactory = configuration.BuildSessionFactory();
}
return _sessionFactory;

执行代码

using (var session = NHibernateHelper.OpenSession())
{
    session.FlushMode = FlushMode.Commit;
    // Start a new transaction
    using (var transaction = session.BeginTransaction())
    {
        // Load employee with Id = 1
        var employee = session.Get<Employee>(1);
        Console.WriteLine($"Before Update: {employee.Name} - IsActive: {employee.IsActive}");
        // Modify the IsActive field to false (this marks the entity as dirty)
        employee.IsActive = false;

        // The entity is now in the session cache with IsActive = false
        Console.WriteLine($"After Update: {employee.Name} - IsActive: {employee.IsActive}");

        // Now, we fetch active employees (IsActive = true)
        var activeEmployees = session.CreateCriteria<Employee>()
                                     .Add(Restrictions.Eq("IsActive", true))
                                     .List<Employee>();

        // Print the active employees (it should still return the modified entity in the session cache)
        Console.WriteLine("\nActive Employees (before commit):");
        foreach (var emp in activeEmployees)
        {
            Console.WriteLine($"{emp.Name} - IsActive: {emp.IsActive}");
        }

        // Commit the transaction to persist changes to the database
        transaction.Commit();
    }
}
// Simulate a new session for the next query (to see changes after commit)
using (var session = NHibernateHelper.OpenSession())
{
    var activeEmployees = session.CreateCriteria<Employee>()
                                 .Add(Restrictions.Eq("IsActive", true))
                                 .List<Employee>();

    // Print the active employees after the session is committed
    Console.WriteLine("\nActive Employees (after commit):");
    foreach (var emp in activeEmployees)
    {
        Console.WriteLine($"{emp.Name} - IsActive: {emp.IsActive}");
    }
}

输出结果

NHibernate:
    SELECT
        employee0_.Id as id1_0_0_,
        employee0_.Name as name2_0_0_,
        employee0_.IsActive as isactive3_0_0_
    FROM
        Employee employee0_
    WHERE
        employee0_.Id=@p0;
    @p0 = 1 [Type: Int32 (0:0:0)]
Before Update: John Doe - IsActive: True
After Update: John Doe - IsActive: False
NHibernate:
    SELECT
        this_.Id as id1_0_0_,
        this_.Name as name2_0_0_,
        this_.IsActive as isactive3_0_0_
    FROM
        Employee this_
    WHERE
        this_.IsActive = @p0;
    @p0 = True [Type: Boolean (0:0:0)]

Active Employees (before commit):
John Doe - IsActive: False
Jane Smith - IsActive: True
Bob Brown - IsActive: True
Alice Green - IsActive: True
NHibernate:
    SELECT
        this_.Id as id1_0_0_,
        this_.Name as name2_0_0_,
        this_.IsActive as isactive3_0_0_
    FROM
        Employee this_
    WHERE
        this_.IsActive = @p0;
    @p0 = True [Type: Boolean (0:0:0)]

Active Employees (after commit):
Jane Smith - IsActive: True
Bob Brown - IsActive: True
Alice Green - IsActive: True

问题原因解析

这是NHibernate的一级缓存(Session缓存)与查询结果合并机制导致的预期行为,具体逻辑如下:

  1. 查询先访问数据库:由于设置了FlushMode.Commit,执行查询时NHibernate不会自动Flush脏数据到数据库,因此查询SQL会从数据库获取所有IsActive=true的记录(此时John Doe在库中仍为true),返回结果包含John Doe的Id。

  2. 一级缓存覆盖查询结果:NHibernate会将查询结果中的实体Id与Session缓存中的实体比对。由于John Doe的实体已存在于Session缓存中(之前通过session.Get<Employee>(1)加载并修改了IsActive为false),NHibernate会直接使用缓存中已修改的实体实例,替换查询结果中对应的记录。

这就导致了最终结果的“矛盾”:查询SQL确实从数据库获取了John Doe(此时库中IsActive为true),但输出时展示的是缓存中已修改的IsActive=false的值。


解决方案

如果需要查询结果严格匹配数据库状态(忽略Session缓存中的未提交修改),可通过以下方式实现:

  • 忽略一级缓存查询:使用SetCacheMode(CacheMode.Ignore)强制查询直接使用数据库返回的数据:

    var activeEmployees = session.CreateCriteria<Employee>()
                                 .Add(Restrictions.Eq("IsActive", true))
                                 .SetCacheMode(CacheMode.Ignore)
                                 .List<Employee>();
    
  • 开启新Session查询:如示例中第二个Session的做法,新Session的一级缓存为空,会完全使用数据库返回的结果。

  • 手动Flush后查询:如果需要让查询包含未提交的修改,可在查询前调用session.Flush(),将脏数据写入数据库(注意这会提前提交修改到库,但事务仍未提交,可回滚)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 03:40:15