Ayende @ Rahien

Oren Eini aka Ayende Rahien CEO of Hibernating Rhinos LTD, which develops RavenDB, a NoSQL Open Source Document Database.

You can reach me by:

oren@ravendb.net

+972 52-548-6969

Posts: 7,019 | Comments: 49,709

filter by tags archive
time to read 3 min | 536 words

Originally posted at 3/30/2011

One thing that we haven’t done so far is manage transactions. I am strongly against having automatic transactions that wrap the entire request. However, I do like automatic transactions that wrap a single action. We can implement this as:

 

public class NHibernateActionFilter : ActionFilterAttribute
{
    private static readonly ISessionFactory sessionFactory = BuildSessionFactory();

    private static ISessionFactory BuildSessionFactory()
    {
        return new Configuration()
            .Configure()
            .BuildSessionFactory();
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var sessionController = filterContext.Controller as SessionController;

        if (sessionController == null)
            return;

        sessionController.Session = sessionFactory.OpenSession();
        sessionController.Session.BeginTransaction();
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        var sessionController = filterContext.Controller as SessionController;

        if (sessionController == null)
            return;

        using (var session = sessionController.Session)
        {
            if (session == null)
                return;

            if (!session.Transaction.IsActive) 
                return;

            if (filterContext.Exception != null)
                session.Transaction.Rollback();
            else
                session.Transaction.Commit();
        }
    }
}

This one is pretty simple, except that you should note that we are checking if the transaction is active before trying something. That is important because we might have got here because of an error in the opening the transaction, we would get a second error which would mask the first.

time to read 5 min | 847 words

Originally posted at 3/30/2011

While I don’t really mind having global state, it tends to bite you on the ass eventually, so let us try to deal with this guy, shall we?

public class NHibernateActionFilter : ActionFilterAttribute
{
    private static readonly ISessionFactory sessionFactory = BuildSessionFactory();

    public static ISession CurrentSession
    {
        get { return HttpContext.Current.Items["NHibernateSession"] as ISession; }
        set { HttpContext.Current.Items["NHibernateSession"] = value; }
    }

    private static ISessionFactory BuildSessionFactory()
    {
        return new Configuration()
            .Configure()
            .BuildSessionFactory();
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        CurrentSession = sessionFactory.OpenSession();
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {

        var session = CurrentSession;
        if (session != null)
        {
            session.Dispose();
        }
    }
}

One easy way to do so would be to put the session directly where we want it to be, in the controller. We already have an extension point for that, the SessionController. Instead of referencing the global session, it can just hold its own:

public class SessionController : Controller
{
    public HttpSessionStateBase HttpSession
    {
        get { return base.Session; }
    }

    public new ISession Session { get; set; }
}

Which leads us to:

public class NHibernateActionFilter : ActionFilterAttribute
{
    private static readonly ISessionFactory sessionFactory = BuildSessionFactory();

    private static ISessionFactory BuildSessionFactory()
    {
        return new Configuration()
            .Configure()
            .BuildSessionFactory();
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var sessionController = filterContext.Controller as SessionController;

        if (sessionController == null)
            return;

        sessionController.Session = sessionFactory.OpenSession();
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        var sessionController = filterContext.Controller as SessionController;

        if (sessionController == null)
            return;
        
        var session = sessionController.Session;
        if (session == null) 
            return;

        session.Dispose();
    }
}

Now we have absolutely no global state, and yet we have a very easy access to the current session.

There are still a few things missing, can you see them?

time to read 5 min | 814 words

Originally posted at 3/30/2011

Remember, we are running on an action scoped session, and this is the code that we run:

public class HomeController : SessionController
{
    public ActionResult Blog(int id)
    {
        var blog = Session.Get<Blog>(id);

        return Json(blog, JsonRequestBehavior.AllowGet);
    }
}

The error that we get is a problem when trying to serialize the Blog. Let us look again the the class diagram:

image

As you can see, we have a collection of Users, but it is lazily loaded. When the Json serializer (which I use instead of writing a view, since it makes my life easier) touches that property, it throws, because you cannot iterate on a lazily loaded collection when the session has already been closed.

One option that we have is to modify the code for Blog like so:

public class Blog
{
    public virtual int Id { get; set; }

    public virtual string Title { get; set; }

    public virtual string Subtitle { get; set; }

    public virtual bool AllowsComments { get; set; }

    public virtual DateTime CreatedAt { get; set; }

    [ScriptIgnore]
    public virtual ISet<User> Users { get; set; }

    public Blog()
    {
        Users = new HashedSet<User>();
    }
}

I don’t like it for several reasons. First, and least among them, serialization duties aren’t one of the responsibilities of the domain model. Next, and most important, is the problem that this approach is incredibly brittle. Imagine what would happen if we added a new lazily loaded property. Suddenly, unless we remembered to add [ScriptIgnore] we would break everything that tried to serialize a Blog instance.

I much rather use an approach that wouldn’t break if I breathed on it. Like the following:

public class HomeController : SessionController
{
    public ActionResult Blog(int id)
    {
        var blog = Session.Get<Blog>(id);

        return Json(new
        {
            blog.AllowsComments,
            blog.CreatedAt,
            blog.Id,
            blog.Subtitle
        }, JsonRequestBehavior.AllowGet);
    }
}

By projecting the values out into a known format, we can save a lot of pain down the road.

But wait a second, this looks quite familiar, doesn’t it? This is the view model pattern, but we arrived at it through an unusual journey.

I don’t really care if you are using anonymous objects or named classes with something like AutoMapper, but I do think that a clear boundary make it easier to work with the application. And if you wonder why you need two models for the same data, the avoidance of accidental queries and the usage of the action scoped session is another motivator.

time to read 1 min | 194 words

Originally posted at 3/30/2011

Previous, our HomeController looked like this:

public class HomeController : SessionController
{
    public ActionResult Blog(int id)
    {
        var blog = Session.Get<Blog>(id);

        return Json(blog, JsonRequestBehavior.AllowGet);
    }
}

My model is defined as:

image

Remember that the previous post we have changed the session management from the request scope to the action scope?

Well, that meant that we have just broken this code…

But can you see how?

time to read 4 min | 795 words

Originally posted at 3/30/2011

In the previous posts, we have looked at how we are going to setup an application using NHibernate. We set it up using:

    public MvcApplication()
    {
        BeginRequest += (sender, args) =>
        {
            CurrentSession = sessionFactory.OpenSession();
        };
        EndRequest += (o, eventArgs) =>
        {
            var session = CurrentSession;
            if (session != null)
            {
                session.Dispose();
            }
        };
    }

But this code is problematic. It is problematic because the session is open for the lifetime of the request. That is a problem, but probably not because of what you think. The #1 reason for issues like Select N+1 is people accessing lazy loaded properties in the views.

One good way of avoiding that is limiting the session scope only to the action, so when rendering the view, the session is not available. Therefor, every attempt to lazy load, will immediately throw. With ASP.Net MVC, this is very easy:

public class NHibernateActionFilter : ActionFilterAttribute
{
    private static readonly ISessionFactory sessionFactory = BuildSessionFactory();

    public static ISession CurrentSession
    {
        get { return HttpContext.Current.Items["NHibernateSession"] as ISession; }
        set { HttpContext.Current.Items["NHibernateSession"] = value; }
    }

    private static ISessionFactory BuildSessionFactory()
    {
        return new Configuration()
            .Configure()
            .BuildSessionFactory();
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        CurrentSession = sessionFactory.OpenSession();
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {

        var session = CurrentSession;
        if (session != null)
        {
            session.Dispose();
        }
    }
}

We need to modify the SessionController as well, so it would now be:

public class SessionController : Controller
{
     public HttpSessionStateBase HttpSession
     {
          get { return base.Session; }
     }

     public new ISession Session { get { return NHibernateActionFilter.CurrentSession; } }
}

Of course, we have broken the HomeController now, but we will discuss how in the next post.

time to read 2 min | 397 words

Originally posted at 3/30/2011

As I mentioned in the previous post, I don’t really like the code as written, so let us see what we can do to fix that. For a start, we have this code:

public class HomeController : Controller
{
    public ActionResult Blog(int id)
    {
        var blog = MvcApplication.CurrentSession.Get<Blog>(id);

        return Json(blog, JsonRequestBehavior.AllowGet);
    }
}

I don’t like the global reference in this manner, so let us see what we can do about it. The easiest way would be to just hide it very well:

public class HomeController : SessionController
{
    public ActionResult Blog(int id)
    {
        var blog = Session.Get<Blog>(id);

        return Json(blog, JsonRequestBehavior.AllowGet);
    }
}

public class SessionController : Controller
{
    public HttpSessionStateBase HttpSession
    {
        get { return base.Session;  }
    }

    public new ISession Session
    {
        get { return MvcApplication.CurrentSession; }
    }
}

So that result in nicer code, the architecture is pretty much the same, but we have a much nicer code for al of our controllers.

We are not done yet, though.

time to read 3 min | 584 words

Originally posted at 3/30/2011

This is part of an exercise that I give in my course. The context is an MVC3 ASP.Net application. Here is how we start things:

public class MvcApplication : System.Web.HttpApplication
{
    private static readonly ISessionFactory sessionFactory = BuildSessionFactory();

    public static ISession CurrentSession
    {
        get{ return HttpContext.Current.Items["NHibernateSession"] as ISession;}
        set { HttpContext.Current.Items["NHibernateSession"] = value; }
    }

    public MvcApplication()
    {
        BeginRequest += (sender, args) =>
        {
            CurrentSession = sessionFactory.OpenSession();
        };
        EndRequest += (o, eventArgs) =>
        {
            var session = CurrentSession;
            if (session != null)
            {
                session.Dispose();
            }
        };
    }

    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        RegisterGlobalFilters(GlobalFilters.Filters);
        RegisterRoutes(RouteTable.Routes);
    }

    private static ISessionFactory BuildSessionFactory()
    {
        return new Configuration()
            .Configure()
            .BuildSessionFactory();
    }
}

And in the controller, we have something like this:

public class HomeController : SessionController
{
    public ActionResult Blog(int id)
    {
        var blog = MvcApplication.CurrentSession.Get<Blog>(id);

        return Json(blog, JsonRequestBehavior.AllowGet);
    }
}

This code is valid, it works, it follows best practices and I actually recommend using something very similar here.

It also annoyed me when I wrote it now, enough to write a series of blog posts detailing how to fix this.

FUTURE POSTS

No future posts left, oh my!

RECENT SERIES

  1. RavenDB Webinar (3):
    01 Jun 2020 - Polymorphism at Scale
  2. Podcast (2):
    28 May 2020 - Adventures in .NET High performance databases with RavenDB with Oren Eini
  3. Webinar recording (4):
    25 May 2020 - Event sourcing and RavenDB
  4. Talk (5):
    23 Apr 2020 - Advanced indexing with RavenDB
  5. Challenge (57):
    21 Apr 2020 - Generate matching shard id–answer
View all series

Syndication

Main feed Feed Stats
Comments feed   Comments Feed Stats