Twitter LinkedIn Github

JetBrains

I've recently started a new internal project and decided to use NHibernate. I've used NHibernate previously on several projects and one of the things I didn't like was the mapping. That's where you waste the most time since and for refactoring it sucks. Every time you need to create a new property or change an existing one, you need to make sure you've updated the XML mapping file.

I had decided to check out the fluent nhibernate project that I had heard about. I thought even though it didn't do away with the mapping, at least you had it in code, so you could easily refactor and do away with some of the mapping errors you initially face. Luckily before I started this project, I ran across Ayende's post on the new Auto-Mapping features of Fluent NHibernate, and I must say, I've been blown away (my twitter followers are probably starting to get sick of me).

I'm now able to do true TDD without having to touch a line of SQL or XML. The only reason I open up SQL Management Studio is to just make sure that the automapping is working correctly, and it is. It handles one to many, many to one, many to many, anything you could think of. And the beauty of it is, that if there is something it can't handle, you can still map it yourself in code.

Take this class:

 

   1: public class Customer
   2: {
   3:     public virtual int Id { get; set; }
   4:     public virtual string NameFirst { get; set; }
   5:     public virtual string NameLast { get; set; }
   6:     public virtual ICollection<Invoice> Invoices { get; set; }
   7: }
   8:  
   9: public class Invoice
  10: {
  11:     public virtual int Id { get; set;}
  12:     public virtual string Number { get; set; }
  13:     public virtual DateTime Date { get; set; }
  14:     // Rest omitted...
  15: }

 

The only thing I have to do, to configure this is:

 

   1: persistenceModel.AddEntityAssembly(assembly)
   2:     .Where(entity => entity.Namespace == "iMeta.Example.Domain.Entities" && entity.GetProperty("Id") != null)
   3:     .WithConvention(convention => convention.GetForeignKeyName = p => p.Name + "Id")
   4:     .Configure(config);

That's it. The only thing that I don't seem to know how to handle yet is components. For now I'm adding some manual mapping (something I'm missing?). So for instance, if Customer has an Address property like so:

   1: public class Address 
   2: {
   3:     public virtual string Street { get; set;}
   4:     public virtual string City { get; set;}
   5:     public virtual string State { get; set;}
   6: }

and the corresponding property on Customer:

   1: public class Customer
   2: {
   3:     ...
   4:     public virtual ICollection<Address> Addresses { get; set; }
   5: }

the resulting configuration would be (line 4 is new):

   1: persistenceModel.AddEntityAssembly(assembly)
   2:     .Where(entity => entity.Namespace == "iMeta.Example.Domain.Entities" && entity.GetProperty("Id") != null)
   3:     .WithConvention(convention => convention.GetForeignKeyName = p => p.Name + "Id")
   4:     .ForTypesThatDeriveFrom<Customer>(map => map.Component<Address>(t => t.Addresses,
   5:                                                                          c =>
   6:                                                                          {
   7:                                                                              c.Map(t => t.Street);
   8:                                                                              c.Map(t => t.City);
   9:                                                                              c.Map(t => t.State);
  10:                                                                          }))
  11:     .Configure(config);

If you don't add this, it will create a table in the database of type Address and reference the customer with a foreign key, as opposed to flattening out Address to fields in the Customer table.

If you combine all this with a call to SchemaExport (Nhibernate Tools namespace), you can have it create your database schema for you. Do this in your tests and you no longer have to worry about cleaning up your database before each test or creating scripts that need to be run as part of your CI build. Very cool!