views:

116

answers:

2

Hi,

I know the question of session management has been brought up in the past, but I could not find anything that helps me overcome my problem..

I have a number of repository classes (e.g CustomerRepository, ProductRepository etc.) which I resolve through Castle Windsor (Note: I am trying to apply the three calls pattern as outlined here). I figure I'd best have a session per Presenter (in my case, this is equivalent to one per form), however, the repository classes need to access the session for the currently active form.. I am not sure how I incorporate this with the fact that these repositories are resolved through windsor, since presenters are not singletons..

For example:

public class SomePresenter
{
  private ISomeView view;
  private ISession session;
  private ICustomerRepository customerRepository;
  private IOrderRepository orderRepository;

  public SomePresenter(ISomeView view, ISessionFactory sessionFactory, ICustomerRepository customerRepository, IOrderRepository orderRepository)
  {
    this.view = view;
    this.session = sessionFactory.OpenSession();
    this.customerRepository = customerRepository;
    this.orderRepository = orderRepository;
  }
}

The repositories needs access to the session... How do I go about this using Windsor? Am I forced to manually set the session on the repositories through a property, or is there a clever Windsor trick that I'm unfamiliar with?

A: 

Why not just have one SessionProvider with individual Data Access Objects (DAO) for each presenter/controller? Your model is accessed through each Data Access Object.

public sealed class SessionProvider
{
        static readonly SessionProvider provider = new SessionProvider();
        private static NHibernate.Cfg.Configuration config;
        private static ISessionFactory factory;
        static ISession session = null;

        /// <summary>
        /// Initializes the <see cref="SessionProvider"/> class.
        /// </summary>
        static SessionProvider() { }

        /// <summary>
        /// Gets the session.
        /// </summary>
        /// <value>The session.</value>
        public static ISession Session
        {
            get
            {
                if (factory == null)
                {
                    config = new NHibernate.Cfg.Configuration();
                    config.Configure();

                    factory = config.BuildSessionFactory();
                }

                if (session == null)
                {                   
                    if (config.Interceptor != null)
                        session = factory.OpenSession(config.Interceptor);
                    else
                        session = factory.OpenSession();
                }

                return session;
            }
        }
    }

public sealed class OrderDataControl
{

        private static ILog log = LogManager.GetLogger(typeof(OrderDataControl));

        private static OrderDataControl orderDataControl;
        private static object lockOrderDataControl = new object();
        /// <summary>
        /// Gets the thread-safe instance
        /// </summary>
        /// <value>The instance.</value>
        public static OrderDataControl Instance
        {
            get
            {
                lock (lockOrderDataControl)
                {
                    if (orderDataControl == null)
                        orderDataControl = new OrderDataControl();
                }
                return orderDataControl;
            }           
        }

        /// <summary>
        /// Gets the session.
        /// </summary>
        /// <value>The session.</value>
        private ISession Session
        {
            get
            {
                return SessionProvider.Session;                
            }
        }


        /// <summary>
        /// Saves the specified contact.
        /// </summary>
        /// <param name="contact">The contact.</param>
        /// <returns></returns>
        public int? Save(OrderItems contact)
        {
            int? retVal = null;
            ITransaction transaction = null;

            try
            {
                transaction = Session.BeginTransaction();
                Session.SaveOrUpdate(contact);

                if (transaction != null && transaction.IsActive)
                    transaction.Commit();
                else
                    Session.Flush();

                retVal = contact.Id;
            }
            catch (Exception ex)
            {
                log.Error(ex);
                if (transaction != null && transaction.IsActive)
                    transaction.Rollback();
                throw;
            }

            return retVal;
        }
0A0D
I'm not a big fan of this approach since it seems to make the code less testable.. I'd rather find a clean way propagate the appropriate session object to the repositories..
Morten Jacobsen
A: 

Why not just inject an ISession into your repositories instead of an ISessionFactory?

Here is the similar code that I use with Autofac, a different IoC container:

containerBuilder
    .Register(c => NHibernateContext.GetSessionFactory().OpenSession())
    .As<ISession>()
    .InstancePerLifetimeScope();

where NHibernateContext is my one and only static class that configures NHibernate and holds onto an ISessionFactory singleton.

So my repository/lookup object asks for a session:

public MyRepository(ISession session)
{
    this.session = session;
}

Then my Presenter/View Model/Superivsing Controller/Whatever-The-Heck-We're-Calling-It-This-Month just gets the repository or lookup object:

public MyPresenter(IWhateverRepository repository)
{
     // Look ma, the repository has an ISession and I'm none the wiser!
}

For Windsor, I think (I'm not terribly familiar with its API, you may have to tweak this but it should give you an idea) it would be something like

container.Register(
    Component.For<ISession>
    .UsingFactoryMethod(
        x => x.Resolve<ISessionFactory>().OpenSession())
    .LifeStyle.Transient);

That is, you tell the container, "When somebody asks for an ISession, run this little delegate that gets the ISessionFactory and opens a session, then give them that ISession instance."

But who closes the ISession? It's up to you: you could have the repository explicitly close the ISession in its own Dispose() method. Or you could rely on your container to do the closing and disposing; in Autofac, I do this with ILifetimeScope and InstancePerLifetimeScope(); in Windsor, I believe you need to look up nested containers, such that when you dispose a child container, all of the components it created are also disposed.

In my experience, this usually means that the container leaks into at least the "main form" of my application: when it's time to create a form, it creates a new lifetime scope/nested container and shows the form. But nothing below this level knows about the container; it's just to throw a lasso around a set of components and say "get rid of all of these when the form is closed."

(This is to prevent just one big honking ISession from being used throughout most of the application. That works fine in ASP.NET, one session per request, but in Windows Forms, as you note, it is like a ticking time bomb for stale object exceptions. Better for each "unit of work" (typically, each form or service) to have its own ISession.)

You could alternatively design your repositories such that each method requires an ISession to be passed in, but that seems like it'd get tedious.

Hope that gives you some ideas. Good luck!

Nicholas Piasecki
Seems like a sound approach... However, I see one problem though (unless I am misinterpreting your intentions ;) ).. What if Mypresenter needs a CustomerRepository and an OrderRepository, in which case they need to share the same session.. Based on your code (correct me if I'm wrong), a new session would be opened for each repository?
Morten Jacobsen
In Autofac there is the concept of a lifetime scope. So I would make sure that the dependencies for my presenter were resolved in the same lifetime scope so that they share the same ISession. In Windsor, you need to look up nested/child containers. I am pretty sure Windsor supports it, but I am hazy on its terminology.using (var childScope = this.container.BeginLifeTimeScope()){ // One ISession created within this scope var presenter = this.childScope.Resolve<MyPresenter>(); // do stuff} // ISession disposed here
Nicholas Piasecki