Using the Conversation per Business Transaction pattern

Another common pattern for session management in desktop applications is Conversation per Business Transaction, often abbreviated as CpBT. In this recipe, I'll show you how to use the CpBT implementation available from the unofficial NHibernate AddIns project, one of many active NHibernate-related open source projects. uNhAddIns was started by NHibernate project leader, Fabio Maulo, and is maintained by several well-known NHibernate contributors.

Getting ready

You'll need to download the latest uNhAddIns project binaries from the project website at http://code.google.com/p/unhaddins/. Extract those binaries from the ZIP file to your solution's Lib folder.

How to do it...

  1. Create a new console application named CpBT.
  2. Add references to Castle.Core, Castle.Windsor, log4net, NHibernate, NHibernate.ByteCode.Castle, uNhAddIns, uNhAddIns.Adapters, uNhAddIns.CastleAdapters, and our Eg.Core project from Chapter 1.
  3. Add an application configuration file with standard log4net and hibernate-configuration sections, just as we did in Chapter 2.
  4. In the hibernate-configuration session-factory element, set current_session_context_class to uNhAddIns.SessionEasier.Conversations.ThreadLocalConversationalSessionContext, uNhAddIns
  5. Add a new folder named DataAccess.
  6. To the DataAccess folder, add an IDao<TEntity> interface with these two methods:
    TEntity Get(Guid Id);
    void Save(TEntity entity);
  7. Add an implementation of IDao<TEntity> named Dao<TEntity> with this code:
    private readonly ISessionFactory _sessionFactory;
    
    public Dao(ISessionFactory sessionFactory)
    {
      _sessionFactory = sessionFactory;
    }
    
    protected ISession Session
    {
      get { return _sessionFactory.GetCurrentSession(); }
    }
    
    public TEntity Get(Guid Id)
    {
      return Session.Get<TEntity>(Id);
    }
    
    public void Save(TEntity entity)
    {
      Session.SaveOrUpdate(entity);
    }
  8. To the CpBT project, add a new folder named ApplicationServices.
  9. To the ApplicationServices folder, add an IEditMovieModel interface with the following methods:
    Movie GetMovie(Guid movieId);
    void SaveMovie(Movie movie);
    void SaveAll();
    void CancelAll();
  10. Add an implementation of IEditMovieModel named EditMovieModel with this code:
    private readonly IDao<Movie> _movieDao;
    
    public EditMovieModel(IDao<Movie> movieDao)
    {
      _movieDao = movieDao;
    }
    
    public virtual Movie GetMovie(Guid movieId)
    {
      return _movieDao.Get(movieId);
    }
    
    public virtual void SaveMovie(Movie movie)
    {
      _movieDao.Save(movie);
    }
    
    public virtual void SaveAll()
    {
    }
    
    public virtual void CancelAll()
    {
    }
  11. Add this using statement: using uNhAddIns.Adapters;
  12. Decorate the EditMovieModel class with the following attribute:
    [PersistenceConversational
  13. Decorate the SaveAll method with the following attribute:
    [PersistenceConversation(ConversationEndMode=EndMode.End)]
  14. Decorate the CancelAll method with the following attribute:
    [PersistenceConversation(ConversationEndMode=EndMode.Abort)]
  15. Add a public static class named ContainerProvider, and add the following using statements:
    using Castle.Facilities.FactorySupport;
    using Castle.MicroKernel.Registration;
    using Castle.Windsor;
    using CpBT.ApplicationServices;
    using CpBT.DataAccess;
    using NHibernate;
    using NHibernate.Engine;
    using uNhAddIns.CastleAdapters;
    using uNhAddIns.CastleAdapters.AutomaticConversationManagement;
    using uNhAddIns.SessionEasier;
    using uNhAddIns.SessionEasier.Conversations;
  16. To configure Castle Windsor for NHibernate and CpBT, add the following code to ContainerProvider:
    private static readonly IWindsorContainer _container;
    
    public static IWindsorContainer Container
    {
      get
      {
        return _container;
      }
    }
    
    static ContainerProvider()
    {
      _container = new WindsorContainer();
      _container
        .AddFacility<PersistenceConversationFacility>();
      _container
        .AddFacility<FactorySupportFacility>();
    
      _container.Register(
        Component.For<ISessionFactoryProvider>()
        .ImplementedBy<SessionFactoryProvider>());
    
      _container.Register(
        Component.For<ISessionFactory>()
          .UsingFactoryMethod(
            () => _container
                    .Resolve<ISessionFactoryProvider>()
                    .GetFactory(null))
        );
    
      _container.Register(
        Component.For<ISessionWrapper>()
        .ImplementedBy<SessionWrapper>());
    
      _container.Register(
        Component.For<IConversationFactory>()
        .ImplementedBy<DefaultConversationFactory>());
    
      _container.Register(
        Component.For<IConversationsContainerAccessor>()
        .ImplementedBy<NhConversationsContainerAccessor>());
    
      _container.Register(
        Component.For(typeof(IDao<>))
          .ImplementedBy(typeof(Dao<>)));
    
      _container.Register(
        Component.For<IEditMovieModel>()
          .ImplementedBy<EditMovieModel>()
          .LifeStyle.Transient);
    }
  17. Add the CreateMovie method to the Program class:
    static Movie CreateNewMovie()
    {
      return new Movie()
      {
      Name = "Hackers",
      Description = "Bad",
      UnitPrice = 12.59M,
      Director = "Iain Softley",
      Actors = new List<ActorRole>()
      {
        new ActorRole() 
        { 
        Actor = "Jonny Lee Miller", 
        Role="Zero Cool"
        },
        new ActorRole() 
        { 
        Actor = "Angelina Jolie", 
        Role="Acid Burn"
        }
      }
      };
    
    }
  18. Finally, add the following code to your main method:
    log4net.Config.XmlConfigurator.Configure();
    var container = ContainerProvider.Container;
    
    Movie movie = CreateNewMovie();
    Guid movieId;
    
    var model = container.GetService<IEditMovieModel>();
    
    model.SaveMovie(movie);
    movieId = movie.Id;
    model.SaveAll();
    movie = null;
    
    movie = model.GetMovie(movieId);
    movie.Description = "Greatest Movie Ever";
    model.CancelAll();
    movie = null;

How it works...

Conversation per Business Transaction (CpBT) introduces the idea of a long-running unit of work with multiple transactions. A single session is held open, perhaps allowing the user several opportunities to interact with the application. Within that session, we may open and commit several transactions, while waiting to save the entire set of changes on the final commit. A typical NHibernate application uses FlushMode.Commit, which means that the unit of work is persisted when the transaction is committed. CpBT uses FlushMode.Never, which means that the unit of work is not automatically persisted when a transaction is committed. Instead, CpBT will explicitly call session.Flush() to persist the unit of work when the conversation has ended.

In a typical use of CpBT, we have a class representing a business transaction, or unit of work, and encapsulating the associated business logic. Each instance serves as a context for our conversation with the database. The conversation can be started, paused, resumed, and either ended or aborted, as shown in this image:

How it works...

The following five conversation actions each handle distinct tasks in our persistent conversation:

  • Start aborts any previous active conversation in this context, then begins a new session and transaction.
  • Resume first starts a conversation if one doesn't already exist in this context, and then starts a new transaction.
  • Pause commits the transaction. Because CpBT uses FlushMode.Never, the unit of work continues and no changes are persisted.
  • End flushes the changes to the database, commits the final transaction, and closes the session.
  • Abort rolls back the transaction and disposes the session.

In this implementation of CpBT, resume is implied at the beginning of each method, as is pause at the end of each method not decorated with end or abort. This automatic CpBT handling is accomplished with the PersistentConversationFacility set up in the ContainerProvider. If Castle would normally return a class decorated with a PersistenceConversational attribute, it will instead return a proxy object. This proxy object handles all of the CpBT actions for us. Thanks to this powerful bit of aspect-oriented programming, we can simply call our business logic methods normally without caring much for the session or transactions. Aspect-oriented programming allows us to separate these cross-cutting concerns, such as session and transaction management, from our true business logic. More information about aspect-oriented programming can be found on Wikipedia at http://en.wikipedia.org/wiki/Aspect-oriented_programming.

In our main method, we have two conversations. The first begins when we save our new instance of the movie Hackers with a call to SaveMovie. The SaveMovie method is wrapped in a transaction. This transaction is automatically committed at the end of the method when the conversation is implicitly paused. However, because CpBT uses FlushMode.Never, the movie was only associated with the session, not written to the database. This transaction is only used to meet NHibernate's requirement regarding transaction usage. It does not persist the unit of work. When we call the SaveAll method, the conversation is resumed and another transaction is started. Because this method is decorated with EndMode.End, when the method ends, CpBT explicitly calls session.Flush(). The unit of work containing the insert of our new Hackers movie is persisted to the database, and finally the transaction is committed.

Because our current conversation just ended, the next conversation begins with the call to GetMovie. A new session and transaction are started, and the Hackers movie is fetched from the database using the movie's ID. When GetMovie ends, the transaction is committed without persisting our (currently empty) unit of work. We then change the movie's description to "Greatest Movie Ever". However, we quickly change our minds and call CancelAll. When we call CancelAll, we abort the session, abandoning our changes to the movie's description.

There's more...

When relying on FlushMode.Never and explicit flushing of the session as we are with CpBT, choose an identity generator that does not require data to be inserted in the database in order for a persistent object identifier (POID) to be generated. The POID generator identity on all RDBMS, as well as native when running on Microsoft SQL Server, will cause your data to be flushed early in the conversation in order to generate an ID value, breaking the unit of work. If you were to abort this conversation, those database changes would not be undone.

uNhAddIns also includes CpBT implementations for the Spring framework, and the PostSharp tool, or with a solid understanding of aspect oriented programming, you can write your own.

See also

  • Setting up session per presenter
  • Using the Burrows framework
..................Content has been hidden....................

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