nHibernate跨Session更新Customer实体报错及SessionFactory模式咨询
一、先解决核心报错:The operation is not valid for the current state of the enlistment
这个报错的根源是跨Session操作游离实体,加上你的SessionFactory实现存在严重问题,导致事务上下文混乱。
1. 问题本质
你从第一个Session获取Customer实体后,该Session被关闭,实体变成游离状态(脱离了原持久化上下文)。之后在第二个Session中调用SaveOrUpdate时,Hibernate无法正确处理跨Session的实体状态同步,再加上你的OpenSession方法每次都重建SessionFactory(重量级对象),进一步加剧了事务上下文的冲突。
2. 两种可行解决方案
方案1:在同一个Session/事务中完成查询+更新
最稳妥的方式是把获取和更新逻辑放在同一个Session的事务里,避免跨Session操作游离实体:
// 修改Service中的更新代码,合并查询与更新逻辑 using (var session = SessionFactoryProvider.OpenSession()) using (var transaction = session.BeginTransaction()) { Customer customer = session.Get<Customer>(customer_payment.customer_id); customer.deductBalance(customer_payment.discount_received); session.SaveOrUpdate(customer); transaction.Commit(); }
方案2:用Merge替代SaveOrUpdate处理游离实体
如果业务逻辑必须拆分到不同Session,那么更新时使用Merge方法,它会自动将游离实体的状态合并到当前Session的持久化上下文中:
// 修改Repository的更新方法 using (ISession session = SessionFactoryProvider.OpenSession()) { using(ITransaction t = session.BeginTransaction()) { session.Merge(customer); // 替换SaveOrUpdate为Merge t.Commit(); } }
Merge的作用是:如果当前Session中没有该实体的持久化实例,会先查询数据库,再把游离实体的属性复制到持久化实例后更新;如果已有持久化实例,则直接同步属性并执行更新,完美解决跨Session的状态冲突。
二、修复SessionFactory的单例问题
你的OpenSession方法每次调用都重新构建SessionFactory,这是严重错误:SessionFactory是重量级对象,构建过程涉及读取配置、映射元数据、初始化数据库连接池等,非常耗资源,多次创建会导致性能暴跌、连接池混乱等问题。
实现单例SessionFactory
可以用静态构造函数实现线程安全的单例:
public class SessionFactoryProvider { private static readonly ISessionFactory _sessionFactory; // 静态构造函数保证只执行一次,线程安全 static SessionFactoryProvider() { Configuration configuration = new Configuration().Configure("hibernate.cfg.xml"); Assembly assembly = Assembly.GetCallingAssembly(); configuration.AddAssembly(assembly); _sessionFactory = configuration.BuildSessionFactory(); // 根据环境配置CurrentSessionContext:Web环境用web,非Web用thread CurrentSessionContext.Bind(_sessionFactory.OpenSession()); } public static ISessionFactory GetSessionFactory() { return _sessionFactory; } public static ISession OpenSession() { // 推荐:优先使用当前绑定的Session(比如Web请求内复用) var currentSession = CurrentSessionContext.CurrentSession; if (currentSession == null || !currentSession.IsOpen) { currentSession = _sessionFactory.OpenSession(); CurrentSessionContext.Bind(currentSession); } return currentSession; // 简单版:直接从单例SessionFactory打开新Session // return _sessionFactory.OpenSession(); } }
三、关于Session的使用建议
- Session是轻量级对象:每次操作新建Session是可以的,但要确保用
using语句及时关闭。在Web环境中,更推荐每个请求绑定一个Session(通过CurrentSessionContext),这样能避免多次创建Session,同时保证请求内的所有操作共享同一个持久化上下文。 - SessionFactory必须是单例:这是Hibernate的最佳实践,唯一的例外是多数据源场景,但单数据源下绝对要保证SessionFactory全局唯一。
额外排查点
检查你的hibernate.cfg.xml中是否配置了正确的current_session_context_class:
<!-- 非Web环境 --> <property name="current_session_context_class">thread</property> <!-- Web环境 --> <property name="current_session_context_class">web</property>
配置错误会导致CurrentSessionContext绑定混乱,进一步引发事务相关的错误。
内容的提问来源于stack exchange,提问作者Dot Net developer

