NHibernate问题:Session缓存与查询结果为何不一致?
问题描述
我们使用NHibernate v4.0.30319,设置FlushMode.Commit。以数据库中含IsActive列的Employee表为例:
在某事务中,通过代码获取empId=1的Employee实体并将其IsActive设为false,此时未提交事务也未执行Flush操作。紧接着执行查询获取所有IsActive=true的员工,结果中仍包含该员工且IsActive值为false。
我无法理解此不一致行为:若查询针对数据库,应返回该员工IsActive为true;若基于一级缓存,则不应包含该员工。为何会出现此现象?
复现环境与代码
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缓存)与查询结果合并机制导致的预期行为,具体逻辑如下:
查询先访问数据库:由于设置了
FlushMode.Commit,执行查询时NHibernate不会自动Flush脏数据到数据库,因此查询SQL会从数据库获取所有IsActive=true的记录(此时John Doe在库中仍为true),返回结果包含John Doe的Id。一级缓存覆盖查询结果: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

