While it’s not open source it’s definitely worth the investment if you use NHibernate because it can give you alerts about any potential issues and information about how to resolve them.
Something that a profiling tool like SQL Profiler just can’t do.
For example by running the code below to insert 200 apples and then retrieve them from the database
Note: this code is meant to be written like this to generate alerts
[Test]
public void NHibernate_Profiler_Demo()
{
using (IStatelessSession session = FruitDatabase.SessionFactory.OpenStatelessSession())
{
using (ITransaction txn = session.BeginTransaction(IsolationLevel.ReadCommitted))
{
for (int i = 0; i < 200; i++)
{
var apple = new Fruit() { Name = "Apple" };
session.Insert(apple);
}
txn.Commit();
}
}
using (IStatelessSession session = FruitDatabase.SessionFactory.OpenStatelessSession())
{
var results = session.Query<Fruit>().ToList();
}
}
the NHibernate Profiler gives a warning about the ‘large number of individual writes’ and a suggestion about having ‘too many database calls per session’.
As you can see in the screenshot there are links to read more which takes you to a takes you to a page with helpful tips to resolve issue and a link to ignore the alert (not recommended!).
This isn’t the only alert that is raised when writing, so you should repeat the cycle of test and fix until no further alerts are raised, the queries/execution plan and/or code have been optimized e.g. changing how/where identifiers are created.
NHibernate Profiler alerts about reads
In addition to detecting problems when writing data the NHibernate profiler can give you tips when reading data.
For the example in this post there are alerts for an unbounded result set and an implicit transaction. Again these probably won’t be the only alerts raised.
Additional features in the NHibernate Profiler include being able to see the result of the query and the execution plan, the ability to compare sessions and a general overview of how an application is interacting with a database.
There are also versions available for other ORMs like Entity Framework, unfortunately you’ll have to buy a separate licence for each one.
So while you could write your own solution to log and analyse interactions between your app and the database, the NHibernate Profiler will save you so much time because it can do the investigative work for you.
Following a recent post I wrote Phillip Haydon made an excellent argument about not using LINQ in ORMs like nHibernate and Entity Framework without consideration to what they are doing.
Something I definitely agree with.
Just as guns don’t kill people, ORMs don’t kill databases. Developers do.
I should have picked up on this because it’s happened to me on a project I was working on.
During development everything was performing well. So well that even the most sceptical developers decided to use ORMs instead of stored procedures.
The application was responsive and users were happy.
However in production there has no happy ending. The application went from running like sh!t off a proverbial shovel to just sh!t.
It was a nightmare and the finger pointing began.
The problem was we assumed (always a bad thing) the queries generated by the ORM would be as good as if we had written them ourselves.
So when we looked at what was being generated it came as a bit of a shock to see what was going on.
But while it was easy to use the ORM as a scapegoat the reality was developers were at fault.
Here’s a simple example of how even the smallest of changes can make a difference in the SQL ORMs generate.
In the code below all we want nHibernate to do is select the of the fruit that is NOT also a color.
[Test]
public void Tale_Of_Two_Queries()
{
var fruitDatabase = new FruitDatabase();
using (ISession session = fruitDatabase.Session)
{
using (ITransaction txn = session.BeginTransaction())
{
var apple = new Fruit() { Name = "Apple", IsAlsoAColor = false};
var orange = new Fruit() { Name = "Orange", IsAlsoAColor = true };
session.Save(apple);
session.Save(orange);
txn.Commit();
}
var firstQuery = session.Query<Fruit>()
.Where(f => !f.IsAlsoAColor)
.Select(f => f.Name)
.ToList();
var secondQuery = session.Query<Fruit>()
.Where(f => f.IsAlsoAColor == false)
.Select(f => f.Name)
.ToList();
}
}
For the query that uses !f.IsAlsoAColor the SQL query looks like this
select fruit0_.Name as col_0_0_
from Fruits fruit0_
where not (fruit0_.IsAlsoAColor = 1)
For the query that uses f.IsAlsoAColor == false the sql generated looks like this
select fruit0_.Name as col_0_0_
from Fruits fruit0_
where case
when fruit0_.IsAlsoAColor = 1 then 1
else 0
end = case
when 0 /* @p0 */ = 1 then 1
else 0
end
While this is a trivial example it highlights the fact you don’t know what the generated SQL will be, let alone the execution plan.
And if your query returns the results you want are you really going to spend time finding out what’s going on ‘under the hood’?
How comes nobody talks about this?
I can’t ever recall a presenter warning about this during any ORM talk I’ve been to. The focus always on what it can do and how it compares to other ORMs rather than best practices.
It’s not just a .Net problem either
After talking to Ruby on Rails developers this is a problem for them too, because Active Record doesn’t work 100% of the time. When it goes wrong they have to implement workarounds to execute queries without using active record.
So how can I make sure it doesn’t happen to me?
The first thing is to know what issues to look out for. The nHibernate profiler alerts page is an excellent resource for this.
With this information you can use profiling tools to see what queries your ORM is generating and how your application is performing.
My last tip is to always use the best data access method for what you’re doing. It’s easy to become complacent or aim to everything using an ORM, but if it makes more sense to use a stored procedure then that’s what you should do.
It would be good to hear what your experiences are with issues like this and to share any tips/tools you have used to avoid ORM performance issues.
In the meantime I’ll leave you with this excellent DBA vs. Developer (Star Wars Style) video
If you’re also going to use the ASP.NET MVC Music Store please ensure you change the ..\Views\Shared\_Layout.cshtml to include HTML to indicate the site is a sample available from codeplex and a noindex robots meta tag.
<!DOCTYPE html>
<html>
<head>
<meta name="robots" content="noindex,nofollow" />
<title>This is a ASP.NET MVC sample application which can be downloaded at http://mvcmusicstore.codeplex.com</title>
<link href="@Url.Content("~/Content/Site.css")" rel="stylesheet"
type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-1.4.4.min.js")"
type="text/javascript"></script>
</head>
<body>
<p style="padding:20px;background-color:#000;color:#FFF;text-align:center">
This is a ASP.NET MVC sample application which can be downloaded at <a href="http://mvcmusicstore.codeplex.com">http://mvcmusicstore.codeplex.com</a>
</p>
<div id="header">
<h1>
<a href="/">ASP.NET MVC MUSIC STORE</a>
</h1>
<ul id="navlist">
<li class="first"><a href="@Url.Content("~")" id="current">Home</a></li>
<li><a href="@Url.Content("~/Store/")">Store</a></li>
<li>@{Html.RenderAction("CartSummary", "ShoppingCart");}</li>
<li><a href="@Url.Content("~/StoreManager/")">Admin</a></li>
</ul>
</div>
@{Html.RenderAction("GenreMenu", "Store");}
<div id="main">
@RenderBody()
</div>
<div id="footer">
built with <a href="http://asp.net/mvc">ASP.NET MVC 3</a>
</div>
</body>
</html>
You can of course workaround this by adding membership tables etc. to the existing SQL Server database by via an alternative membership provider which includes using another application hosted on AppHarbor.