Using NHibernate with TransactionScope

Reliable integration with other systems is a common business requirement. When these systems report error conditions, it's necessary to roll back not only the local database work, but perhaps the work of multiple transactional resources. In this recipe, I'll show you how to use Microsoft's TransactionScope and NHibernate to achieve this goal.

Getting ready

Create a new console application project.

Add references to the Eg.Core project in Chapter 1, NHibernate.dll, and NHibernate.ByteCode.Castle.dll.

Get the console application ready by following the Configuring NHibernate with App.config and Configuring log4net recipes in Chapter 2.

How to do it...

  1. Add a reference to System.Transaction.
  2. Add a public interface named IReceiveProductUpdates with the following three methods:
    void Add(Product product);
    void Update(Product product);
    void Remove(Product product);
  3. Add a public class named WarehouseFacade with this code:
    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. Add a public class named ProductCatalog with this code:
    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. Update the Program class with the following code:
    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. Build and run your application. You should see this output:
    How to do it...
  7. Check the NHCookbook database. You should find a Product row for Sprockets with a unit price of $9.99.

How it works...

In this recipe, we work with two services that receive product updates. The first, a product catalog, uses NHibernate to store product data. The second, a small facade, is not as well-defined. It could use a number of different technologies to integrate our application with the larger warehouse system it represents.

Our services allow us to add, update, and remove products in these two systems. By wrapping these changes in a TransactionScope, we gain the ability to roll back the product catalog changes if the warehouse system fails, maintaining a consistent state.

Remember that NHibernate requires an NHibernate transaction when interacting with the database. TransactionScope is not a substitute. As illustrated in the next image, the TransactionScope should completely surround both the session and NHibernate transaction. The call to TransactionScope.Complete() should occur after the session has been disposed. Any other order will most likely lead to nasty, production crashing bugs like connection leaks.

How it works...

When we attempt to remove a product, our WarehouseFacade throws an exception, and things get a little strange. We committed the NHibernate transaction, so why didn't our delete happen? It did, but it was rolled back by the TransactionScope. When we started our NHibernate transaction, NHibernate detected the ambient transaction created by the TransactionScope and enlisted. The underlying connection and database transaction were held until the TransactionScope committed, or in this case, rolled back.

See also

  • Creating a session ASP.NET MVC action filter
  • Creating a transaction ASP.NET MVC action filter
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.219.220.22