In my last article on the topic of database development, I covered performing database migration using MigratorDotNET.
Next, I wanted to look at the mapping process to the object model. I’d already decided I was going to use NHibernate as my ORM, but the detail was in hooking up NHibernate to the database. NHibernate’s XML syntax is pretty straightforward and incredibly powerful, but once again it’s another language i’m forced to deal with – i’m most efficient working in my primary language so the idea of slowing down when dealing with these mapping files by hand was really not in question.
Initially I looked into ActiveRecord (admittedly not into a whole lot of detail) but wasn’t excited by the framework. ActiveRecord performs it’s mappings from objects to database through attributes and it just seemed to me like an abuse of SoC. If i should want to change my ORM (unlikely, but i’m working within that constraint on this project), then it would have to support the same attribute syntax. Having said that however, there is a lot of momentum behind ActiveRecord, so i’m still reserving judgement on it’s applicability.
So apart from ActiveRecord, I went in search of another alternative to the XML mapping and recalled reading about Fluent NHibernate. In a nut-shell, Fluent NHibernate is a replacement for the XML mapping layer in NHibernate by defining the mapping in C#. Essentially the same benefits I got by using MigratorDotNet (type safety, compile-time checking etc) were available for defining my DB->OM mapping. Sweet!
A very quick spike with the project, and I immediately liked what I saw:
public class NoteMap : ClassMap<Note> { public NoteMap() { Id(x => x.NoteId).GeneratedBy.Guid(); Map(x => x.NoteTitle).WithLengthOf(64); Map(x => x.NoteData); } }
The equivalent XML mapping file (I won’t discuss it in detail in this post) would have been at least twice the size, and more importantly not refactor-friendly.
Because it’s in C# I was easily able to unit-test this mapping, with a little assistance. In fact it was so successful, it helped me discover a bug in my database migration script!
[TestFixture] public class NoteMapping_Test : BaseTestMappings { [Test] public void TestCanAddNote() { Note note = new Note { NoteTitle = "Title", NoteData = "Data`" }; Session.Save(note); Session.Flush(); Session.Clear(); Note fromDb = Session.Get<Note>(note.NoteId); Assert.AreNotSame(note, fromDb); Assert.AreEqual(note.NoteData, fromDb.NoteData); Assert.AreEqual(note.NoteTitle, fromDb.NoteTitle); Assert.AreEqual(note.NoteId, fromDb.NoteId); } } public class BaseTestMappings { protected SessionSource Source { get; set; } protected ISession Session { get; private set; } [SetUp] public void SetUp() { Source = new SessionSource(new TestModel()); Session = Source.CreateSession(); Source.BuildSchema(Session); CreateInitialData(Session); Session.Clear(); Session.Transaction.Begin(); } [TearDown] public void TearDown() { Session.Transaction.Rollback(); Session.Close(); Session.Dispose(); } public class TestModel : PersistenceModel { public TestModel() { Assembly ass = typeof(NoteMap).Assembly; addMappingsFromAssembly(ass); } } }
What’s happening here is that Fluent NHibernate allows me to instantiate an NHibernate instance just by creating a Model. The model contains a list of all of the mappings applicable for my application and I pass that directly into the NHibernate session. Any of my tests which I want connected to a database will now have transaction management and a session for performing querying. I can use this in my application too with just about the exact same code, so instantiate a session and pass in the model.
It works very well and i’ve sucessfully removed the XML file with a type-safe C# mapping engine. The problem this poses, however is now I have two places where I have defined what my data structures look like. One in the MigratorDotNET framework, and the other in FluentNHibernate to map the data model. This means that to make any any change to the model would involve no less than 3 changes – the POCO, FluentNHibernate and MigratorDotNET.
Next time, I want to discuss ways of reducing this friction and streamline the refactoring process.
(There are some websites which i wish to attribute for some of the code and ideas i’ve expressed here but have since lost the links. If you see anything that’s yours, please let me know so I can appropriately credit)