如何在NHibernate中添加嵌套事务?求.NET多配置事务回滚代码示例
嘿,得先给你捋清楚一个核心点:常规的数据库事务一旦提交,就没法再回滚了。所以你想要的“后续配置项处理失败时,回滚所有已经提交的独立事务”,直接用NHibernate的普通嵌套事务是实现不了的——因为那些已经提交的事务已经把数据持久化到数据库里了。不过我们有两种可行的方案来满足你的业务需求,下面给你详细拆解:
方案1:使用分布式事务(DTC)
如果你的数据库支持分布式事务协调器(DTC),可以把所有配置项的操作纳入一个全局事务中。这样每个配置项的操作虽然看起来是独立执行,但实际上都属于同一个全局事务的一部分,只有当所有操作都成功时才会统一提交,任何一步出错都会全局回滚。
NHibernate可以配合.NET的TransactionScope来实现这个逻辑,代码示例如下:
using System.Transactions; using NHibernate; using System.Collections.Generic; public class ConfigService { private readonly ISessionFactory _sessionFactory; public ConfigService(ISessionFactory sessionFactory) { _sessionFactory = sessionFactory; } public void ProcessMultipleConfigs(List<ConfigItem> configItems) { // 创建全局事务范围,自动管理全局事务的生命周期 using (var scope = new TransactionScope(TransactionScopeOption.Required)) { foreach (var configItem in configItems) { try { using (var session = _sessionFactory.OpenSession()) { // 处理单个配置项的业务逻辑 ProcessSingleConfig(session, configItem); } } catch (Exception ex) { // 抛出异常触发全局回滚 throw new InvalidOperationException($"处理配置项{configItem.Id}失败", ex); } } // 所有操作无异常,提交全局事务 scope.Complete(); } } private void ProcessSingleConfig(ISession session, ConfigItem configItem) { var existingConfig = session.Get<ConfigItem>(configItem.Id); if (existingConfig != null) { existingConfig.Value = configItem.Value; session.Update(existingConfig); } else { session.Save(configItem); } // 刷新会话确保操作纳入全局事务 session.Flush(); } } // 示例配置项实体 public class ConfigItem { public virtual int Id { get; set; } public virtual string Key { get; set; } public virtual string Value { get; set; } }
注意:使用DTC需要确保你的数据库和应用服务器都开启了DTC服务,这种方案在高并发场景下可能有性能开销,因为全局事务的锁持有时间更长。
方案2:补偿事务模式
如果你的场景不适合用DTC(比如性能要求高、数据库不支持DTC),可以采用补偿事务的思路:每个配置项执行成功后,记录对应的“补偿操作”;如果后续步骤出错,就执行这些补偿操作来撤销之前的修改。
代码示例如下:
using NHibernate; using System.Collections.Generic; public class ConfigService { private readonly ISessionFactory _sessionFactory; // 存储已执行操作的补偿逻辑 private readonly List<Action<ISession>> _compensations = new List<Action<ISession>>(); public void ProcessMultipleConfigs(List<ConfigItem> configItems) { using (var session = _sessionFactory.OpenSession()) { try { foreach (var configItem in configItems) { ProcessSingleConfigWithCompensation(session, configItem); } // 所有操作成功,提交最终事务 using (var tx = session.BeginTransaction()) { tx.Commit(); } // 清空补偿列表,无需再执行 _compensations.Clear(); } catch (Exception ex) { // 执行补偿操作,回滚已完成的修改 using (var tx = session.BeginTransaction()) { foreach (var compensation in _compensations) { compensation(session); } tx.Commit(); } throw new InvalidOperationException("处理配置项失败,已执行补偿回滚", ex); } } } private void ProcessSingleConfigWithCompensation(ISession session, ConfigItem configItem) { using (var tx = session.BeginTransaction()) { var existingConfig = session.Get<ConfigItem>(configItem.Id); if (existingConfig != null) { // 记录原有值,作为补偿逻辑 var originalValue = existingConfig.Value; _compensations.Add(s => { var configToRollback = s.Get<ConfigItem>(configItem.Id); configToRollback.Value = originalValue; s.Update(configToRollback); }); // 更新配置项 existingConfig.Value = configItem.Value; session.Update(existingConfig); } else { // 新增配置的补偿逻辑:删除该配置 _compensations.Add(s => { var configToDelete = s.Get<ConfigItem>(configItem.Id); if (configToDelete != null) { s.Delete(configToDelete); } }); session.Save(configItem); } tx.Commit(); } } }
这个方案的核心是:每完成一个配置项的修改,就把“如何撤销这个修改”的逻辑存起来;一旦后续出错,就逐个执行这些撤销操作,把数据恢复到之前的状态。
注意:补偿事务需要你自己确保补偿逻辑的正确性,而且如果在执行补偿操作时又出错,可能需要额外的重试或人工介入机制。
内容的提问来源于stack exchange,提问作者Ayush
相关产品推荐
相关产品推荐

