This is part 4 of my series on ASP.NET MVC and NHibernate. If you’re not up to date, you can go check out:
- Part 1 – Setting up the Visual Studio solution
- Part 2 – Building the model
- Part 3 – Mapping the model to the database
As promised, today, we’re going to test our mappings and get a little familiar with using NHibernate.
We’ll be using NUnit 2.5.2, but any recent version should work.
Disclaimer: I’m still learning some of this myself, so use at your own risk. This may not be considered best practice. Also, there’s almost certainly better ways to write these tests using one of the dozens of popular testing frameworks out there, but we’re using plain vanilla NUnit.
Let’s create a new Class Library project for our tests. We’ll call it NStackExample.Data.Tests. Now, in the data test project, add references to your core project, data project, NHibernate.dll, FluentNHibernate.dll, and NUnit.Framework.dll. If you’ve installed NUnit, NUnit.Framework.dll will be on the .NET tab. If you have multiple versions of NUnit installed, be sure to pick the right version.
SQL: Now in a convenient travel size
If you haven’t heard of SQLite before, you’re going to love this. It’s a tiny, self-contained, open-source SQL database engine in a DLL. Even better, it can run entirely in-memory and it’s blazing fast. Here’s how you get set up to use it:
- Download the SQLite ADO.NET Provider from here. Get the full version – the one named something like SQLLit-1.0.65.0-setup.exe. Install it, then grab a copy of System.Data.Sqlite.dll and put it in your Solution Items folder with all the other 3rd party libraries. If you’re running on a 64-bit operating system, grab the one from the bin\x64 directory. If not, use the one in bin.
- Download the SQLite library itself. Scroll down to Precompiled Binaries for Windows. It should be named something like sqlitedll-3_6_17.zip. Extract the SQLite3.dll to your Solution Items folder.
- In your data test project, add a reference to System.Data.SQLite.dll.
- Because SQLite3.dll was written in C and is completely unmanaged, we can’t add a direct reference to it. To ensure it gets put in the right place, we’re going to set it up as a content file. Right click on your data test project, choose Add Existing Item, then browse for SQLite3.dll. Add it. In Solution Explorer, it’ll be mixed in with the code for your project. Right click on it and choose properties. Set it to Copy Always. This will copy it to the bin\Debug or bin\Release folder every time your project is built, so it never gets forgotten.
- If you haven’t already, grab the code for the SQLiteDatabaseScope class from my previous post. Add it to your data test project.
A simple mapping test
Imports NUnit.Framework
<TestFixture()> _
Public Class CourseMappingTests
<Test()> _
Public Sub CanLoadAndSaveCourse()
Using Scope As New SQLiteDatabaseScope(Of CourseMapping)
Using Session = Scope.OpenSession
Dim ID As Guid
Dim Course As Course
Using Tran = Session.BeginTransaction
ID = Session.Save(New Course With { _
.Subject = "SUBJ", _
.CourseNumber = "1234", _
.Title = "Title", _
.Description = "Description", _
.Hours = 3})
Tran.Commit()
End Using
Session.Clear()
Using Tran = Session.BeginTransaction
Course = Session.Get(Of Course)(ID)
Assert.AreEqual("SUBJ", Course.Subject)
Assert.AreEqual("1234", Course.CourseNumber)
Assert.AreEqual("Title", Course.Title)
Assert.AreEqual("Description", Course.Description)
Assert.AreEqual(3, Course.Hours)
Tran.Commit()
End Using
End Using
End Using
End Sub
End Class
using System;
using NUnit.Framework;
using NHibernate;
namespace NStackExample.Data.Tests
{
[TestFixture]
public class CourseMappingTests
{
[Test]
public void CanSaveAndLoadCourse()
{
using (SQLiteDatabaseScope<CourseMapping> Scope = new SQLiteDatabaseScope<CourseMapping>())
{
using (ISession Session = Scope.OpenSession())
{
Guid ID;
Course Course;
using (ITransaction Tran = Session.BeginTransaction())
{
ID = (Guid)Session.Save(new Course
{
Subject = "SUBJ",
CourseNumber = "1234",
Title = "Title",
Description = "Description",
Hours = 3
});
Tran.Commit();
}
Session.Clear();
using (ITransaction Tran = Session.BeginTransaction())
{
Course = Session.Get<Course>(ID);
Assert.AreEqual("SUBJ", Course.Subject);
Assert.AreEqual("1234", Course.CourseNumber);
Assert.AreEqual("Title", Course.Title);
Assert.AreEqual("Description", Course.Description);
Assert.AreEqual(3, Course.Hours);
Tran.Commit();
}
}
}
}
}
}
Here’s how it works:
- First, we get a fresh in-memory SQLite database with our schema built.
- Put a new course in the database
- Clear the session
- Get the course back out of the database
- Check to make sure each of our properties survived the trip. If they didn’t, fail the test.
There’s a few things that may be new to you.
- Our class has the TestFixture attribute. This tells NUnit that this class contains tests.
- Each subroutine has the Test attribute. This tells NUnit that this method is a test.
- The SQLiteDatabaseScope is almost certainly new, considering I wrote it Friday. You can read my previous post for more information.
Use of implicit transaction is discouraged
You’re probably wondering why I would wrap such simple one-statement database logic in a transaction, especially a Session.Get, which is essentially a single select statement. Prior to writing this series, I wouldn’t have done it that way. Rookie mistake.
While doing research for this entry, I ran across an example test from Ayende. He was using transactions on everything, even his calls to Session.Get. I asked him why and he sent me a link to this NHProfiler Alert. It’s important and not obvious – at least not to me – so with permission, I’ve quoted the entire page.
A common mistake when using a database is to use transactions only when orchestrating several write statements. In reality, every operation that the database is doing is done inside a transaction, including queries and writes (update, insert, delete).
When we don’t define our own transactions, it falls back into implicit transaction mode, where every statement to the database runs in its own transaction, resulting in a large performance cost (database time to build and tear down transactions), and reduced consistency.
Even if we are only reading data, we should use a transaction, because using transactions ensures that we get consistent results from the database. Hibernate assumes that all access to the database is done under a transaction, and strongly discourages any use of the session without a transaction.
Session session = sessionFactory.openSession(); try { Transaction tx = session.beginTransaction(); try { //execute code that uses the session } finally { tx.commit(); } } finally { session.close(); }Leaving aside the safety issue of working with transactions, the assumption that transactions are costly and that we need to optimize them is false. As previously mentioned, databases are always running in a transaction. Also, they have been heavily optimized to work with transactions.
The real question here is: Is the transaction per-statement or per-batch? There is a non-trivial amount of work that needs to be done to create and dispose of a transaction; having to do it per-statement is more costly than doing it per-batch.
It is possible to control the number and type of locks that a transaction takes by changing the transaction isolation level (and, indeed, a common optimization is to reduce the isolation level).
Hibernate treats the call to commit() as the time to flush all changed items from the unit of work to the database, and without an explicit call to Commit(), it has no way of knowing when it should do that. A call to Flush() is possible, but it is frowned upon because this is usually a sign of improper transaction usage.
I strongly suggest that you use code similar to that shown above (or use another approach to transactions, such as TransactionScope, or Castle’s Automatic Transaction Management) in order to handle transactions correctly.
Transaction and the second level cache
Another implication of not using explicit transactions with Hibernate is related to the use of the second level cache.
Hibernate goes to great length in order to ensure that the 2nd level cache maintains a consistent view of the database. This is accomplished by deferring all 2nd level cache updates to the transaction commit. In this way, we can assert that the data in the 2nd level cache is the one committed to the database.
Forgoing the use of explicit transactions has the effect of nulling the 2nd level cache. Here is an example that would make this clear:
try { Post post = session.get(Post.class, 1); // do something with post } finally { session.close(); }Even if the 2nd level cache is enabled for Post, it is still not going to be cached in the 2nd level cache. The reason is that until we commit a transaction, Hibernate will not update the cache with the values for the loaded entities.
This code, however, does make use of the 2nd level cache:
Session session = sessionFactory.openSession(); try { Transaction tx = sessionFactory.beginTransaction(); try { Post post = session.get(Post.class, 1); // do something with post } finally { tx.commit(); } } finally { session.close(); }
A slightly more complicated mapping test
When an entity has a required parent, as in the case of our section, you must create and insert the parent before actually testing the child. We’re not testing the cascade here. That’s a separate test. In this case, section has two required parents: a course, and a term. Here’s the test:
<Test()> _
Public Sub CanLoadAndSaveCourse()
Using Scope As New SQLiteDatabaseScope(Of CourseMapping)
Using Session = Scope.OpenSession
Dim ID As Guid
Dim Section As Section
Dim Course As New Course With { _
.Subject = "SUBJ", _
.CourseNumber = "1234", _
.Title = "Title", _
.Description = "Description", _
.Hours = 3}
Dim Term As New Term With { _
.Name = "Fall 2009", _
.StartDate = New Date(2009, 9, 1), _
.EndDate = New Date(2009, 12, 1)}
'We're not testing the cascade, so save the parents first...
Using Tran = Session.BeginTransaction
Session.Save(Course)
Session.Save(Term)
Tran.Commit()
End Using
Session.Clear()
Using Tran = Session.BeginTransaction
ID = Session.Save(New Section With { _
.Course = Course, _
.FacultyName = "FacultyName", _
.RoomNumber = "R1", _
.SectionNumber = "1W", _
.Term = Term})
Tran.Commit()
End Using
Session.Clear()
Using Tran = Session.BeginTransaction
Section = Session.Get(Of Section)(ID)
Assert.AreEqual(Course, Section.Course)
Assert.AreEqual("FacultyName", Section.FacultyName)
Assert.AreEqual("R1", Section.RoomNumber)
Assert.AreEqual("1W", Section.SectionNumber)
Assert.AreEqual(Term, Section.Term)
Tran.Commit()
End Using
End Using
End Using
End Sub
[Test]
public void CanSaveAndLoadSection()
{
using (SQLiteDatabaseScope<CourseMapping> Scope = new SQLiteDatabaseScope<CourseMapping>) {
using (ISession Session = Scope.OpenSession()) {
Guid ID;
Section Section;
Course Course = new Course {
Subject = "SUBJ",
CourseNumber = "1234",
Title = "Title",
Description = "Description",
Hours = 3};
Term Term = new Term {
Name = "Fall 2009",
StartDate = new DateTime(2009,8,1),
EndDate = new DateTime(2009,12,1)};
// We're not testing the cascade here, so explicitly save these parent objects.
using (ITransaction Tran = Session.BeginTransaction()) {
Session.Save(Course);
Session.Save(Term);
Tran.Commit();
}
Session.Clear();
using (ITransaction Tran = Session.BeginTransaction()) {
ID = (Guid) Session.Save(new Section {
Course = Course,
FacultyName = "FacultyName",
RoomNumber = "R1",
SectionNumber = "W1",
Term = Term});
Tran.Commit();
}
Session.Clear();
using (ITransaction Tran = Session.BeginTransaction()) {
Section = Session.Get<Section>(ID);
Assert.AreEqual(Course, Section.Course);
Assert.AreEqual("FacultyName", Section.FacultyName);
Assert.AreEqual("R1",Section.RoomNumber);
Assert.AreEqual("W1", Section.SectionNumber);
Assert.AreEqual(Term, Section.Term);
Tran.Commit();
}
}
}
}
Testing the cascade
“Cascade what? “
In your application, when you’ve just registered a student for a whole bunch of classes, usually with several changes along the way, you don’t want to have to remember what entities were added, removed or changed. That’s just crazy. Thanks to the Cascade functionality in NHibernate, you don’t have to do that. Just save the student entity. If your mappings are correct, it just works™.
For some people, especially me, that’s a big if. That’s why we test our mappings.
<Test()> _
Public Sub CanCascadeSaveFromCourseToSections()
Using Scope As New SQLiteDatabaseScope(Of CourseMapping)
Using Session = Scope.OpenSession
Dim ID As Guid
Dim Term As New Term With { _
.Name = "Fall 2009", _
.StartDate = New Date(2009, 9, 1), _
.EndDate = New Date(2009, 12, 1)}
'We're not testing the cascade of section -> term here
Using Tran = Session.BeginTransaction
Session.Save(Term)
Tran.Commit()
End Using
Session.Clear()
Dim Course As New Course With { _
.Subject = "SUBJ", _
.CourseNumber = "1234", _
.Title = "Title", _
.Description = "Description", _
.Hours = 3}
Dim Section1 As New Section With { _
.FacultyName = "FacultyName", _
.RoomNumber = "R1", _
.SectionNumber = "1", _
.Term = Term}
Dim Section2 As New Section With { _
.FacultyName = "FacultyName", _
.RoomNumber = "R1", _
.SectionNumber = "2", _
.Term = Term}
Course.AddSection(Section1)
Course.AddSection(Section2)
'Test saving
Using Tran = Session.BeginTransaction
ID = Session.Save(Course)
Tran.Commit()
End Using
Session.Clear()
'Check the results
Using Tran = Session.BeginTransaction
Course = Session.Get(Of Course)(ID)
Assert.AreEqual(2, Course.Sections.Count)
Assert.AreEqual(1, Course.Sections _
.Where(Function(S As Section) _
S.Equals(Section1)) _
.Count(), "Course.Sections does not contain section 1.")
Assert.AreEqual(1, Course.Sections _
.Where(Function(S As Section) _
S.Equals(Section2)) _
.Count(), "Course.Sections does not contain section 2.")
Tran.Commit()
End Using
End Using
End Using
End Sub
[Test()]
public void CanCascadeSaveFromCourseToSections()
{
using (SQLiteDatabaseScope Scope = new SQLiteDatabaseScope())
{
using (ISession Session = Scope.OpenSession())
{
Guid ID;
Term Term = new Term {
Name = "Fall 2009",
StartDate = new System.DateTime(2009, 9, 1),
EndDate = new System.DateTime(2009, 12, 1) };
//We're not testing the cascade of section -> term here
using (ITransaction Tran = Session.BeginTransaction())
{
Session.Save(Term);
Tran.Commit();
}
Session.Clear();
Course Course = new Course {
Subject = "SUBJ",
CourseNumber = "1234",
Title = "Title",
Description = "Description",
Hours = 3 };
Section Section1 = new Section {
FacultyName = "FacultyName",
RoomNumber = "R1",
SectionNumber = "1",
Term = Term };
Section Section2 = new Section {
FacultyName = "FacultyName",
RoomNumber = "R1",
SectionNumber = "2",
Term = Term };
Course.AddSection(Section1);
Course.AddSection(Section2);
//Test saving
using (ITransaction Tran = Session.BeginTransaction())
{
ID = (Guid) Session.Save(Course);
Tran.Commit();
}
Session.Clear();
//Check the results
using (ITransaction Tran = Session.BeginTransaction())
{
Course = Session.Get(ID);
Assert.AreEqual(2, Course.Sections.Count);
Assert.AreEqual(1, Course.Sections
.Where(S => S.Equals(Section1)).Count(),
"Course.Sections does not contain section 1.");
Assert.AreEqual(1, Course.Sections
.Where(S => S.Equals(Section2)).Count(),
"Course.Sections does not contain section 2.");
Tran.Commit();
}
}
}
}
The test above will make sure new and/or updated sections are saved when you save the course. Here’s how it works:
- Get a fresh SQLite DB
- Since we’re not testing terms, but we need one for our sections, build a term and stick it in the database.
- Build a course and two sections.
- Save the course
- Clear the session
- Get the course
- Make sure it has our two sections
What should happen when you remove a section from a course? A parent course is required for each section. Remember, we specified not nullable in the mapping. More importantly, an orphaned section isn’t allowed in the real world. So, if a section is orphaned, it should be deleted. We need to write a test for that.
<Test()> _
Public Sub CanCascadeOrphanDeleteFromCourseToSections()
Using Scope As New SQLiteDatabaseScope(Of CourseMapping)
Using Session = Scope.OpenSession
Dim ID As Guid
Dim Term As New Term With { _
.Name = "Fall 2009", _
.StartDate = New Date(2009, 9, 1), _
.EndDate = New Date(2009, 12, 1)}
Using Tran = Session.BeginTransaction
'We're not testing the cascade of section -> term here
Session.Save(Term)
Tran.Commit()
End Using
Session.Clear()
Dim Course As New Course With { _
.Subject = "SUBJ", _
.CourseNumber = "1234", _
.Title = "Title", _
.Description = "Description", _
.Hours = 3}
Dim Section1 As New Section With { _
.FacultyName = "FacultyName", _
.RoomNumber = "R1", _
.SectionNumber = "1", _
.Term = Term}
Dim Section2 As New Section With { _
.FacultyName = "FacultyName", _
.RoomNumber = "R1", _
.SectionNumber = "2", _
.Term = Term}
Course.AddSection(Section1)
Course.AddSection(Section2)
Using Tran = Session.BeginTransaction
Session.Save(Course)
Tran.Commit()
End Using
Session.Clear()
'Test removing
Course.RemoveSection(Section1)
Using Tran = Session.BeginTransaction
ID = Session.Save(Course)
Tran.Commit()
End Using
Session.Clear()
'Check the results
Using Tran = Session.BeginTransaction
Course = Session.Get(Of Course)(ID)
Assert.AreEqual(1, Course.Sections.Count())
Assert.AreEqual(0, Course.Sections _
.Where(Function(S As Section) _
S.Equals(Section1)) _
.Count(), "Course.Sections still contains section 1")
Tran.Commit()
End Using
End Using
End Using
End Sub
[Test()]
public void CanCascadeOrphanDeleteFromCourseToSections()
{
using (SQLiteDatabaseScope<CourseMapping> Scope = new SQLiteDatabaseScope<CourseMapping>())
{
using (ISession Session = Scope.OpenSession())
{
Guid ID;
Term Term = new Term {
Name = "Fall 2009",
StartDate = new System.DateTime(2009, 9, 1),
EndDate = new System.DateTime(2009, 12, 1) };
using (ITransaction Tran = Session.BeginTransaction())
{
//We're not testing the cascade of section -> term here
Session.Save(Term);
Tran.Commit();
}
Session.Clear();
Course Course = new Course {
Subject = "SUBJ",
CourseNumber = "1234",
Title = "Title",
Description = "Description",
Hours = 3 };
Section Section1 = new Section {
FacultyName = "FacultyName",
RoomNumber = "R1",
SectionNumber = "1",
Term = Term };
Section Section2 = new Section {
FacultyName = "FacultyName",
RoomNumber = "R1",
SectionNumber = "2",
Term = Term };
Course.AddSection(Section1);
Course.AddSection(Section2);
using (ITransaction Tran = Session.BeginTransaction())
{
Session.Save(Course);
Tran.Commit();
}
Session.Clear();
//Test removing
Course.RemoveSection(Section1);
using (ITransaction Tran = Session.BeginTransaction())
{
ID = (Guid) Session.Save(Course);
Tran.Commit();
}
Session.Clear();
//Check the results
using (ITransaction Tran = Session.BeginTransaction())
{
Course = Session.Get<Course>(ID);
Assert.AreEqual(1, Course.Sections.Count());
Assert.AreEqual(0, Course.Sections
.Where(S => S.Equals(Section1)).Count(),
"Course.Sections still contains section 1");
Tran.Commit();
}
}
}
}
I hope you see where I’m going with this one. Except for query tests, which we’ll do when we write our DAOs, that’s it for NHibernate testing. We do the same types of tests for our other entity classes.
But…
So, I bet you’re thinking “This mess won’t compile and even if it did, almost all of your tests would fail!” Yep. If the tests always pass, why write them?
Normally, I’d at least declare those missing functions so the solution would compile, but in this case, the discussion of those issues fits better with our next topic: How do we fix the broken stuff?
Download links for the complete solution in both languages are coming soon.
Edit: Download VB.NET here or C# here. To simplify things, I’ve removed the StudentTerm entity from the model.
Jason
- Testy and in need of sleep.
References: Ayende’s blog post, Krzysztof Kozmic’s recent post on Devlio.us, Daniel Hoebling’s blog post, Ayende’s NHProfiler Alerts.




#1 by Yassir on August 16th, 2009
Please source code hurry
Pingback: ASP.NET MVC Archived Blog Posts, Page 1
#2 by Dave Hanna on August 17th, 2009
Make sure the namespace names are consistent between the test classes and the SQLiteDatabaseScope class. One had NStackExamplesTest (plural examples), and another had NStackExampleTests (plural tests). Drove me crazy trying to figure out why SQLiteDatabaseScope was an unrecognized name.
#3 by Jason on August 17th, 2009
Sorry about that. I also noticed I zipped up the wrong stuff in the VB download. I’ll fix it when I get home tonight.
#4 by Dave Hanna on August 17th, 2009
I’ve worked through several errors, but I’m stuck on this one:
” Could not create the driver from NHibernate.Driver.SQLite20Driver, NHibernate, Version=2.1.0.4000, Culture=neutral, PublicKeyToken=aa95f207798dfdb4. —>”
Do I have the wrong version of NHibernate? It looks like it’s asking for SQLite 2.0, and I’ve downloaded and included 3.0. Is that the source of my problem? I think I have everything as directed.
#5 by Jason on August 17th, 2009
My first guess is that you’re using the System.Data.SQLite.dll file from the download on a 32-bit operating system. That seems to cause all sorts of random errors. The one I’ve included is the 64-bit version. You’ll need to swap them out, reload the project, and recompile.
Please let me know if that doesn’t solve your issue.
Second guess would be a slight difference between your NHibernate.dll and mine, which would cause the public key tokens to not match. This assumes that you’ve replaced the NHibernate.dll in the download with your own, which is probably not the case.
#6 by Dave Hanna on August 17th, 2009
For a newbie, please explain the advantage of the third party NUnit testing framework over the the Micrsoft UnitTesting framework included in VS? Does it do something that Microsoft’s doesn’t?
I was able to get the able to get the tests to compile (at least the CourseMappingTests – haven’t tried the rest yet) simply by 3 mechanical renamings – changing using NUnitFramework to using Microsoft.VisualStudio.TestTools.UnitTesting, change [TestFramework] to [TestClass()], and changing [Test] to [TestMethod()].
I’m suspecting there’s probably something that I don’t know that explains the advantage of NUnit, but I don’t know enough to know what I don’t know!
#7 by Jason on August 17th, 2009
To be perfectly honest, I use NUnit because it’s what I’ve always used – since before MS had anything for testing. I’ve seen some posts that give reasons why NUnit, or basically anything else, is better than MSTest. As with a lot of things (see also Entity Framework), it’s hard to separate the truth from the Microsoft-bashing.
NUnit does everything I need it to do and nothing more. Until I see a lot of buzz around something else, I’m not shopping for a new test runner / framework. I would suggest you google for the NUnitEx project. It has great fluent extensions for testing, so your tests are a million times more readable.
Trackback: DotNetShoutout
#8 by yassir on August 17th, 2009
@jason : Well i prefer MsTest (NUnit , xUnit , MbUnit are all great) ,but with MsTest i don’t need to get extra dlls and worry about a test runner everything is part of visual studio (i think standard , Pro , and team system has it) and if you have team system you can get code covrege
Pingback: Reflective Perspective - Chris Alcock » The Morning Brew #414
#9 by Scott Blasingame on August 18th, 2009
Got the test suite running, but had to add a reference to NHibernate.ByteCode.Castle.dll in Tests project. Also System.Data.SQLite reference was not set to Copy Local. After fixing those 2 things, test for CanSaveAndLoadCourse passed.
Keep up the great postings!
#10 by Jason on August 18th, 2009
I should have held on to that post a little longer, but not seeing any free time in the next few days, I felt it would be better to get something out the door than to let the series die on the vine.
The code downloads are not clean. Some of the mistakes were intentional to use as examples for part 5. Others were unintentional.
#11 by Dave Hanna on August 21st, 2009
My problem with the SQLite reference was the one Scott found – the reference to Sqlite in the test project had Copy Local set to False (default). Changing that to true made the test build and run.
#12 by Chuck on August 22nd, 2009
J –
Check out PersistenceSpecification in Fluent NHibernate.
e.g.
[Test]
public void CanCorrectlyMapAddressType()
{
using (ISession session = this.sessionFactory.OpenSession())
{
using (ITransaction tx = session.BeginTransaction())
{
new PersistenceSpecification(session)
.CheckProperty(x => x.Name, “Home Address”)
.VerifyTheMappings();
tx.Rollback();
}
}
}
#13 by Chuck on August 22nd, 2009
Of course, Type Pad changes the angle brackets, but look up PersistenceSpecification on the FNH Wiki
C
Pingback: Part 5: Fixing the Broken Stuff « BASICly everything
Pingback: Part 5: Fixing the Broken Stuff - NHibernate blog - NHibernate Forge
#14 by Jason on August 23rd, 2009
@Chuck – That’s really cool. This is another example of “My practices, not necessarily best practices.” I’m learning a lot from this series. I hope others are as well.
Pingback: Part 6: Ninject and MVC or How to be a Web Ninja - NHibernate blog - NHibernate Forge
Pingback: Part 7: NHibernate and Ninject for ASP.NET MVC - NHibernate blog - NHibernate Forge