ASP.NET Mini Profiler with NHibernate

Download source: https://github.com/marufbd/DefaultDDDArch
source background reference: http://freakin-mind.blogspot.com/2012/01/default-ddd-architecture-with-auto.html

I was just curious whether I could watch my Nhibernate queries running whenever I visit my mvc application pages. I wanted to see even the redirect traces. Then I came to know that the Stackoverflow team has done that and using on their production sites already. And its free and open source. A similar commercial product dedicated to Nhibernate profiling is there called NhProfiler.

The profiler: http://code.google.com/p/mvc-mini-profiler/

Problem is I could not find how to use it with NHibernate. Searched and found one solution at: http://blog.fearofaflatplanet.me.uk/mvcminiprofiler-and-nhibernate

Here I demonstrate how I implement the solution in my own project. For a bit of a background about the project source, see my previous post.

The profiler installation for your project is easy.
Just install the nuget package in your web mvc project. like this:

clip_image001

You see I am adding into the mvc web project and selected MiniProfiler.MVC3. Later I will show when the MiniProfiler package is required to be added in our MyFramework project where the repository codes are written. This installation adds webactivator and a folder app_start where the miniprofiler code is placed. You will see in that file an HttpModule is writtent and the module is started dynamically by :

DynamicModuleUtility.RegisterModule(typeof(MiniProfilerStartupModule));


Now, I need to add MiniProfiler package in MyFramework project where the NHibernate session is started:


clip_image001[9]

Now I need to add the driver class for sqlce at Myframework\NHib\ProfiledSqlCeDriver.cs as:



#region REFERENCES

using System.Data;
using System.Data.Common;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;
using MvcMiniProfiler;
using MvcMiniProfiler.Data;

#endregion REFERENCES

namespace MyFrameWork.NHib
{
public class ProfiledSqlCeDriver : NHibernate.Driver.SqlServerCeDriver
{
public override IDbCommand CreateCommand()
{
IDbCommand command = base.CreateCommand();

if (MiniProfiler.Current != null)
command = DbCommandProxy.CreateProxy(command);

return command;
}

public class DbCommandProxy : RealProxy
{
private DbCommand instance;
private IDbProfiler profiler;

private DbCommandProxy(DbCommand instance)
: base(typeof(DbCommand))
{
this.instance = instance;
this.profiler = MiniProfiler.Current as IDbProfiler;
}

public override IMessage Invoke(IMessage msg)
{
IMethodCallMessage methodMessage = new MethodCallMessageWrapper((IMethodCallMessage)msg);

var executeType = GetExecuteType(methodMessage);

if (executeType != ExecuteType.None)
profiler.ExecuteStart(instance, executeType);

object returnValue = methodMessage.MethodBase.Invoke(instance, methodMessage.Args);

if (executeType == ExecuteType.Reader)
returnValue = new ProfiledDbDataReader((DbDataReader)returnValue, instance.Connection, profiler);

IMessage returnMessage = new ReturnMessage(returnValue, methodMessage.Args, methodMessage.ArgCount, methodMessage.LogicalCallContext, methodMessage);

if (executeType == ExecuteType.Reader)
profiler.ExecuteFinish(instance, executeType, (DbDataReader)returnValue);
else if (executeType != ExecuteType.None)
profiler.ExecuteFinish(instance, executeType);

return returnMessage;
}

private static ExecuteType GetExecuteType(IMethodCallMessage message)
{
switch (message.MethodName)
{
case "ExecuteNonQuery":
return ExecuteType.NonQuery;
case "ExecuteReader":
return ExecuteType.Reader;
case "ExecuteScalar":
return ExecuteType.Scalar;
default:
return ExecuteType.None;
}
}

public static IDbCommand CreateProxy(IDbCommand instance)
{
var proxy = new DbCommandProxy(instance as DbCommand);

return proxy.GetTransparentProxy() as IDbCommand;
}
}
}
}



Now add the driver class in nHibernate.config file in the web project:


<!--<property name="connection.driver_class">NHibernate.Driver.SqlServerCeDriver</property>-->
<property name="connection.driver_class">MyFrameWork.NHib.ProfiledSqlCeDriver, MyFramework</property>


Now running the web project and browse Publishers:


clip_image001[14]

You see in red it identified to duplicate queries. Its because NHibernate is using lazy loading and in the view for each Publisher we are accessing the Books collection property like:


@item.Books.Count


That is the classic n+1 problem.


How do I resolve it. I decided to put it as a convention in the framework to eager load a batch of 5 entities whenever there is a has many collection. So our Myframework\NHib\Conventions\HasManyConvention.cs becomes with a single line added as:



using FluentNHibernate.Conventions;
using FluentNHibernate.Conventions.Instances;

namespace MyFrameWork.NHib.Conventions
{
public class HasManyConvention : IHasManyConvention
{
public void Apply(IOneToManyCollectionInstance instance)
{
instance.Key.Column(instance.EntityType.Name + "Fk");

//This line is added as new
instance.BatchSize(5);

instance.Cascade.All();
}
}
}


 

Now browsing Publishers shows ok in profiler:


clip_image001[16]

Now, we can understand the benefit of the recommended convention based framework developed here. You see by only writing a single line of code, we were able to eliminate the n+1 problem for all domain entities in the framework. This also demonstrates a situation where we can put our convention based logic. There is always the option to override any setting for individual entity by MapOverrides, for details go to my post here.

Comments

Popular Posts