Setting up session per presenter

In desktop applications using the model-view-presenter pattern, it's best to use a session for each presenter. This approach can also be adapted to the model-view-view model pattern. More information on these patterns is available at http://en.wikipedia.org/wiki/Model-view-presenter.

In this recipe, I'll show you how to implement this session-per-presenter pattern with dependency injection.

Getting ready

You'll need the named scope extension to Ninject available at http://github.com/remogloor/ninject.extensions.namedscope.

Download the source code in ZIP format and extract it. Open the Ninject.Extensions.NamedScope.sln solution in Visual Studio and build the solution. Copy Ninject.dll and Ninject.Extensions.NamedScope.dll from the builddebug folder to our Cookbook solution's Lib folder.

If you're not familiar with the dependency injection concept, a free video tutorial is available from TekPub at http://tekpub.com/view/concepts/1.

Note

This recipe can be completed with other dependency injection frameworks. Just substitute the NinjectBindings class with an equivalent configuration for your favorite DI framework.

How to do it...

  1. Add a new console project to the solution named SessionPerPresenter.
  2. Add references to the Eg.Core project from Chapter 1, NHibernate.dll, NHibernate.ByteCode.Castle.dll, Ninject.dll, and Ninject.Extensions.NamedScope.dll.
  3. Add an App.config file and set up the NHibernate and log4net configuration sections. Refer to the Configuring NHibernate and Configuring NHibernate logging recipes in Chapter 2.
  4. Add a folder to the new project named Data.
  5. In the Data folder, create an IDao<TEntity> interface with the following code:
    public interface IDao<TEntity> : IDisposable
      where TEntity : class
    {
      IEnumerable<TEntity> GetAll();
    }
  6. Create an implementation with the following code:
    public class Dao<TEntity> : IDao<TEntity>
      where TEntity : class 
    {
      private readonly ISessionProvider _sessionProvider;
      public Dao(ISessionProvider sessionProvider)
      {
        _sessionProvider = sessionProvider;
      }
      public void Dispose()
      {
        _sessionProvider.Dispose();
      }
      public IEnumerable<TEntity> GetAll()
      {
        var session = _sessionProvider.GetCurrentSession();
        IEnumerable<TEntity> results;
        using (var tx = session.BeginTransaction())
        {
          results = session.QueryOver<TEntity>()
            .List<TEntity>();
          tx.Commit();
        }
        return results;
      }
    }
  7. In the Data folder, create an ISessionProvider interface with the following code:
    public interface ISessionProvider : IDisposable
    {
      ISession GetCurrentSession();
      void DisposeCurrentSession();
    }
  8. Create an implementation with the following code:
    public class SessionProvider
      : ISessionProvider
    {
      private readonly ISessionFactory _sessionFactory;
      private ISession _currentSession;
      public SessionProvider(ISessionFactory sessionFactory)
      {
        Console.WriteLine("Building session provider");
        _sessionFactory = sessionFactory;
      }
      public ISession GetCurrentSession()
      {
        if (null == _currentSession)
          _currentSession = _sessionFactory.OpenSession();
        return _currentSession;
      }
      public void DisposeCurrentSession()
      {
        _currentSession.Dispose();
        _currentSession = null;
      }
      public void Dispose()
      {
        if (_currentSession != null)
          _currentSession.Dispose();
        _currentSession = null;
      }
    }
  9. Create a Ninject module named NinjectBindings with the following code:
    public class NinjectBindings : NinjectModule 
    {
      public override void Load()
      {
        const string presenterScope = "PresenterScope";
        var asm = GetType().Assembly;
        var presenters =
          from t in asm.GetTypes()
          where typeof (IPresenter).IsAssignableFrom(t) &&
                t.IsClass && !t.IsAbstract
          select t;
        foreach (var presenterType in presenters)
          Kernel.Bind(presenterType)
            .ToSelf()
            .DefinesNamedScope(presenterScope);
        Kernel.Bind<ISessionProvider>()
          .To<SessionProvider>()
          .InNamedScope(presenterScope);
        Kernel.Bind(typeof(IDao<>))
          .To(typeof(Dao<>));
      }
    }
  10. In the root of our project, create a ProductListView class with the following code:
    public class ProductListView
    {
      private readonly string _description;
      private readonly IEnumerable<Product> _products;
      public ProductListView(
        string description,
        IEnumerable<Product> products)
      {
        _description = description;
        _products = products;
      }
      public void Show()
      {
        Console.WriteLine(_description);
        foreach (var p in _products)
          Console.WriteLine(" * {0}", p.Name);
      }
    }
  11. Create a public IPresenter interface inherited from IDisposable. This interface can be left empty.
  12. Create a MediaPresenter class with the following code:
    public class MediaPresenter : IPresenter 
    {
      private readonly IDao<Movie> _movieDao;
      private readonly IDao<Book> _bookDao;
      public MediaPresenter(IDao<Movie> movieDao, 
        IDao<Book> bookDao)
      {
        _movieDao = movieDao;
        _bookDao = bookDao;
      }
      public ProductListView ShowBooks()
      {
        return new ProductListView("All Books", 
          _bookDao.GetAll().OfType<Product>());
      }
      public ProductListView ShowMovies()
      {
        return new ProductListView("All Movies",
          _movieDao.GetAll().OfType<Product>());
      }
      public void Dispose()
      {
        _movieDao.Dispose();
        _bookDao.Dispose();
      }
    }
  13. Create a ProductPresenter class with the following code:
    public class ProductPresenter : IPresenter 
    {
      private readonly IDao<Product> _productDao;
      public ProductPresenter(IDao<Product> productDao)
      {
        _productDao = productDao;
      }
      public ProductListView ShowAllProducts()
      {
        return new ProductListView("All Products",
          _productDao.GetAll());
      }
      public virtual void Dispose()
      {
        _productDao.Dispose();
      }
    }
  14. In Program.cs, in the Main method, add the following code:
    var nhConfig = new Configuration().Configure();
    var sessionFactory = nhConfig.BuildSessionFactory();
    
    var kernel = new StandardKernel();
    kernel.Load(new Data.NinjectBindings());
    kernel.Bind<ISessionFactory>()
      .ToConstant(sessionFactory);
    
    var media1 = kernel.Get<MediaPresenter>();
    var media2 = kernel.Get<MediaPresenter>();
    
    media1.ShowBooks().Show();
    media2.ShowMovies().Show();
    
    media1.Dispose();
    media2.Dispose();
    
    using (var product = kernel.Get<ProductPresenter>())
    {
      product.ShowAllProducts().Show();
    }
    
    Console.WriteLine("Press any key");
    Console.ReadKey();
  15. If you like, create some test product, book, and movie data in the NHCookbook database.
  16. Build and run your application. You will see the following output:
    How to do it...

How it works...

There are several interesting items in this recipe to discuss. First, we've set up a slightly complex object graph. For each instance of MediaPresenter, our graph appears as shown in the next image:

How it works...

In the previous image, one instance of session provider is shared by both data access objects. This is accomplished with the configuration of Ninject, our dependency injection framework

In our NinjectBindings, we match up our service interfaces to their matching implementations. We bind the open generic IDao<> interface to Dao<>, so that requests for IDao<Book> are resolved to Dao<Book>, IDao<Movie> to Dao<Movie>, and so on.

One session per presenter is accomplished with the use of DefinesNamedScope and InNamedScope. We find all of the IPresenter implementations in the assembly. Each presenter is bound and defines the PresenterScope. When we bind ISessionProvider to SessionProviderImpl, we use InNamedScope("PresenterScope") to indicate that we will have only one session provider per presenter.

A simple call to Kernel.Get<MediaPresenter>() will return a new presenter instance all wired up and ready to use. It will have two data access objects sharing a common session provider. To close the session and release any lingering database connections, be sure to call Dispose() when you're finished with the presenter.

A typical Save method on a Dao may look something like this:

var session = _sessionProvider.GetCurrentSession();
try
{
  session.SaveOrUpdate(entity);
}
catch (StaleObjectStateException)
{
  _sessionProvider.DisposeCurrentSession();
  throw;
}

Notice how we are immediately throwing away the session in the catch block. When NHibernate throws an exception from inside a session call, the session's state is undefined. The only remaining operation you can safely perform on that session is Dispose(). This allows us to recover gracefully from any exceptions, as the exploded session is already thrown away, so a fresh session can take its place.

You should also take care with entities still associated with this failed session. It's usually a good idea to attach them to the new session, as any operation, including lazy loading, against the failed session will cause further exceptions. The session.Merge recipe mentioned later in this chapter discusses a method for accomplishing this.

There's More...

Because the boundaries are not as well-defined as in a web application, there are two very common anti-patterns for handling NHibernate sessions in desktop applications. The first, a singleton session, has the following problems:

  • Undefined point for flushing the session to the database
  • Untestable interactions between unrelated parts of the application
  • It is impossible to recover gracefully from a StaleObjectExceptions or other session-exploding exceptions
  • A stateful singleton is always bad architecture

The second, a micro-session, where a session is opened to perform a single operation and then quickly closed loses all of the benefits of the unit of work, most notably the session cache. Entities will be constantly re-fetched from the database.

See also

  • Using the Conversation per Business Transaction pattern
  • Using session.Merge
..................Content has been hidden....................

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