Using NHibernate with TransactionScope
通过事务范围来使用NHibernate
与其他系统的可靠集成是挺常见的业务需求.在这些系统报错时,不只本地数据库的事务需要回滚,也许其他的多个事务资源都需要.本节介绍如何使用微软的TransactionScope和NHibernate来达成该目标.
准备
- 创建一个控制台应用程序.
- 添加引用:第一章中的Eg.Core项目,NHibernate.dll和NHibernate.ByteCode.Castle.dll.
- 参照Configuring NHibernate with App.config和Configuring log4net小节来完成该控制台程序.
步骤
1. 添加引用System.Transaction .
2. 添加一个名为IReceiveProductUpdates 的公共接口,该接口有3个方法,代码如下:
void Add(Product product); void Update(Product product); void Remove(Product product);
3. 添加一个名为WarehouseFacade的公共类,代码如下:
public class WarehouseFacade : IReceiveProductUpdates { public void Add(Product product) { Console.WriteLine("Adding {0} to warehouse system.", product.Name); } public void Update(Product product) { Console.WriteLine("Updating {0} in warehouse system.", product.Name); } public void Remove(Product product) { Console.WriteLine("Removing {0} from warehouse system.", product.Name); var message = string.Format( "Warehouse still has inventory of {0}.", product.Name); throw new ApplicationException(message); } }
4. 添加一个名为ProductCatalog的公共类,代码如下:
public class ProductCatalog : IReceiveProductUpdates { private readonly ISessionFactory _sessionFactory; public ProductCatalog(ISessionFactory sessionFactory) { _sessionFactory = sessionFactory; } public void Add(Product product) { Console.WriteLine("Adding {0} to product catalog.", product.Name); using (var session = _sessionFactory.OpenSession()) using (var tx = session.BeginTransaction()) { session.Save(product); tx.Commit(); } } public void Update(Product product) { Console.WriteLine("Updating {0} in product catalog.", product.Name); using (var session = _sessionFactory.OpenSession()) using (var tx = session.BeginTransaction()) { session.Update(product); tx.Commit(); } } public void Remove(Product product) { Console.WriteLine("Removing {0} from product catalog.", product.Name); using (var session = _sessionFactory.OpenSession()) using (var tx = session.BeginTransaction()) { session.Delete(product); tx.Commit(); } } }
5. 更新Program类,代码如下:
class Program { static void Main(string[] args) { var nhConfig = new Configuration() .Configure(); var sessionFactory = nhConfig .BuildSessionFactory(); var catalog = new ProductCatalog(sessionFactory); var warehouse = new WarehouseFacade(); var p = new Program(catalog, warehouse); var sprockets = new Product() { Name = "Sprockets", Description = "12 pack, metal", UnitPrice = 14.99M }; p.AddProduct(sprockets); sprockets.UnitPrice = 9.99M; p.UpdateProduct(sprockets); p.RemoveProduct(sprockets); Console.WriteLine("Press any key."); Console.ReadKey(); } private readonly IReceiveProductUpdates[] _services; public Program(params IReceiveProductUpdates[] services) { _services = services; } private void AddProduct(Product newProduct) { Console.WriteLine("Adding {0}.", newProduct.Name); try { using (var scope = new TransactionScope()) { foreach (var service in _services) service.Add(newProduct); scope.Complete(); } } catch (Exception ex) { Console.WriteLine("Product could not be added."); Console.WriteLine(ex.Message); } } private void UpdateProduct(Product changedProduct) { Console.WriteLine("Updating {0}.", changedProduct.Name); try { using (var scope = new TransactionScope()) { foreach (var service in _services) service.Update(changedProduct); scope.Complete(); } } catch (Exception ex) { Console.WriteLine("Product could not be updated."); Console.WriteLine(ex.Message); } } private void RemoveProduct(Product oldProduct) { Console.WriteLine("Removing {0}.", oldProduct.Name); try { using (var scope = new TransactionScope()) { foreach (var service in _services) service.Remove(oldProduct); scope.Complete(); } } catch (Exception ex) { Console.WriteLine("Product could not be removed."); Console.WriteLine(ex.Message); } } }
6. 编译运行,结果如下图所示:
7. 查看NHCookbook数据库,将会看到一行产品数据:单价为$9.99的Sprockets.
原理
这个示例中,我们有两个接收product更新的服务,第一个,ProductCatalog 使用NHibernate存储product数据.第二个,WarehouseFacade,一个小的外观定义模式,她并不是定义良好的,她通过所代表的更大的warehouse系统,使用许多不同的技术整合我们的应用程序.
在这两个系统中的服务,提供了add, update和remove产品的功能.通过将这些功能封装到一个TransactionScope中,提供了回滚功能:在warehouse系统失败时,回滚对product catalog所做的修改,并且维护了一个一致的状态.
NHibernate要求在和数据库交互时必须使用事务.TransactionScope并不是替代者.请看下图,TransactionScope完全涵盖了会话和NHibernate事务.应该在会话注销后调用TransactionScope.Complete().顺序不对的话,很可能导致像连接漏洞的bug.
当移除一个product时,WarehouseFacade会抛出一个异常,奇怪的状况发生了.我们提交了NHibernate事务,但是为什么不能进行delete操作?实际上,delete操作发生了,但是被TransactionScope回滚取消了.启用NHibernate事务后,NHibernate会检测到由TransactionScope创建或征用的相关事务.本实例中,底层连接和数据库事务会被保持到TransactionScope提交,或者被回滚.