Setting up session-per-presenter

It's a good idea to use a session for each presenter in desktop applications using the Model View Presenter (MVP) pattern. This approach can also be adapted to the Model View View Model (MVVM) pattern. More information on these patterns is available at http://en.wikipedia.org/wiki/Model–view–presenter and http://en.wikipedia.org/wiki/Model–view–viewmodel.

In this recipe, we'll show you a crude implementation of this session-per-presenter pattern with dependency injection. While MVP and MVVM are more common in Windows Forms and WPF applications, we will just create a simple console application this time.

We will use an inversion of the control container, called Ninject, in this recipe. If you're not familiar with the dependency injection or Inversion of Control concepts, a free video tutorial is available at http://tinyurl.com/iocvideo.

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 a reference to NH4CookbookHelpers, Ninject and Ninject.Extensions.NamedScope using NuGet Package Manager Console.
  3. Add a folder to the new project named Data.
  4. In the Data folder, create an IDao<TEntity> interface with the following code:
    using System;
    using System.Collections.Generic;
    
    namespace SessionPerPresenter.Data
    {
      public interface IDao<TEntity> : IDisposable
      where TEntity : class
      {
        IEnumerable<TEntity> GetAll();
      }
    }
  5. Create an implementation with the following code:
    using System.Collections.Generic;
    
    namespace SessionPerPresenter.Data
    {
      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;
        }
      }
    }
  6. In the Data folder, create an ISessionProvider interface with the following code:
    using System;
    using NHibernate;
    
    namespace SessionPerPresenter.Data
    {
      public interface ISessionProvider : IDisposable
      {
        ISession GetCurrentSession();
        void DisposeCurrentSession();
      }
    }
  7. Create an implementation with the following code:
    using System;
    using NHibernate;
    
    namespace SessionPerPresenter.Data
    {
      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)
          {
            Console.WriteLine("Opening session");
            _currentSession = _sessionFactory.OpenSession();
          }
          return _currentSession;
        }
        public void DisposeCurrentSession()
        {
          _currentSession.Dispose();
          _currentSession = null;
        }
        public void Dispose()
        {
          if (_currentSession != null)
          {
            Console.WriteLine("Disposing session");
            _currentSession.Dispose();
          }
          _currentSession = null;
        }
      }
    }
  8. Create a ProductListView class with the following code:
    using System;
    using System.Collections.Generic;
    using NH4CookbookHelpers.Queries.Model;
    
    namespace SessionPerPresenter
    {
      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);
        }
      }
    }
  9. Create a public IPresenter interface inherited from IDisposable. This interface can be left empty:
    using System;
    
    namespace SessionPerPresenter
    {
      public interface IPresenter : IDisposable
      {
      }
    }
  10. Create a MediaPresenter class with the following code:
    using System.Linq;
    using NH4CookbookHelpers.Queries.Model;
    using SessionPerPresenter.Data;
    
    namespace SessionPerPresenter
    {
      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();
        }
      }
    }
  11. Create a ProductPresenter class with the following code:
    using NH4CookbookHelpers.Queries.Model;
    using SessionPerPresenter.Data;
    
    namespace SessionPerPresenter
    {
      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();
        }
      }
    }
  12. In the root of our project, create a class named NinjectBindings with the following code:
    using System.Linq;
    using Ninject.Extensions.NamedScope;
    using Ninject.Modules;
    using SessionPerPresenter.Data;
    
    namespace SessionPerPresenter
    {
      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<>));
        }
      }
    }
  13. In Program.cs, add the following code in the Main method:
    var sessionFactory = ProductModel
      .CreateExampleSessionFactory(true);
    var kernel = new StandardKernel();
    kernel.Load(new 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();
  14. 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 following figure:

How it works…

In the previous figure, 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 DefinedNamedScope 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 similar to this:

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

Note how we are immediately throwing away the session (not the provider itself) 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…

Since 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
  • Interactions, which cannot be tested, 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, since consuming code can't rely on the state being consistent between calls

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 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.144.40.63