Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Letting Ninject manage my transaction state, practice concerns

I'm letting Ninject manage my ISession and ITransaction state in Fluent nHibnerate with the following registration method - I am wondering if it is sufficient control of transactions, or whether I need to be putting this somewhere else.

The thought is that each ISession is created on a request, and that Ninject handles the commit of everything done during that request.

public class SessionModule : Ninject.Modules.NinjectModule
{
    private static ISessionFactory sessionFactory;

    public override void Load()
    {
        Bind<ISessionFactory>()
            .ToMethod(c => CreateSessionFactory())
            .InSingletonScope();

        Bind<ISession>()
            .ToMethod(c => OpenSession())
            .InRequestScope()
            .OnActivation(session =>
            {
                session.BeginTransaction();
                session.FlushMode = FlushMode.Commit;
            })
            .OnDeactivation(session =>
            {
                if (session.Transaction.IsActive)
                {
                    try
                    {
                        session.Flush();
                        session.Transaction.Commit();
                    }
                    catch
                    {
                        session.Transaction.Rollback();
                    }
                }
            });
    }

    /// <summary>
    /// Create a new <see cref="NHibernate.ISessionFactory"/> to connect to a database.
    /// </summary>
    /// <returns>
    /// A constructed and mapped <see cref="NHibernate.ISessionFactory"/>.
    /// </returns>
    private static ISessionFactory CreateSessionFactory()
    {
        if (sessionFactory == null)
            sessionFactory = Persistence.SessionFactory.Map
                (System.Web.Configuration
                    .WebConfigurationManager
                    .ConnectionStrings["Local"]
                    .ConnectionString
                );
        return sessionFactory;
    }

    /// <summary>
    /// Open a new <see cref="NHibernate.ISession"/> from a <see cref="NHibernate.ISessionFactory"/>.
    /// </summary>
    /// <returns>
    /// A new <see cref="NHibernate.ISession"/>.
    /// </returns>
    private static ISession OpenSession()
    {
        // check to see if we even have a session factory to get a session from
        if (sessionFactory == null)
            CreateSessionFactory();

        // open a new session from the factory if there is no current one
        return sessionFactory.OpenSession();
    }
}

I've examined the runtime using System.Diagnostics.Debug.WriteLine to write when things occur, and it does look like this is doing what I wanted it to do. What I am asking you, the community, is whether this is a good practice or not. Here is my understanding.

Countless hours of reading on http://ayende.com/blog/default.aspx has led me to re-evaluate a lot of the way I do session management.

A lot of digging into the nHibernate documentation tells me I need to use ITransaction every single time anything happens with my database.

Placing the management in an attribute is considered a flaw, since it does not adhere to the above mentioned statement.

Doing ITransaction per individual operation is not the correct process, because it would require either (A) my Controllers to have access to the ISession or (B) My IRepository<T> to have the ITransaction logic, which I have been told in previous questions was not a good practice.

Placing my ITransaction Management in an HttpModule adds un-neccessary overhead, since it gives my HttpContext knowledge of the ISession and it means I have to do some kind of injection into the HttpRequest (which I can do using [Inject], but it doesn't seem wise)

This has led me to this conclusion.

  • Transactions should start when an ISession is requested.
  • Everything that happens in a single request is encapsulated by one ISession
  • When an ITransaction is done, it needs to be committed so that the 2nd Level Cache can get its results.

Can anyone shed light on this? Am I finally on the right track? Or have I still missed the point completely?

like image 680
Ciel Avatar asked Mar 19 '11 16:03

Ciel


1 Answers

I'm no expert (and have no experience with ninject), but I do agree with your 3 conclusions, and that's what I do in my projects.
Another thing I can add is that, in my opinion, transactions should be controlled EXPLICITLY and per operation, and not globally (start and beginning of request and commit at the end) like your code suggests.
This is because I believe you want to control your transaction's behaviour- commit or not (or maybe not even start, if no DB access is necessary) for every operation individually.
What I use is a management (or workflow, if you prefer) layer, which is responsible just for that. for example:

public class SomeManager : ManagersBase
{
    public void DoSomething(DomainObject obj)
    {
        if (obj.Operation())
        {
            using (ITransaction tx = Session.BeginTransaction())
            {
                try
                {
                    Session.Update(obj);
                    tx.Commit();
                }
                catch (MeaningfulException ex)
                {
                    //handle
                    tx.Rollback();
                }
            }
        }
    }
}

hope this helps

like image 182
J. Ed Avatar answered Nov 21 '22 20:11

J. Ed