Automatically building FluentNHibernate mappings

As those who suffered data access and passed to the ORM world know, one of the key benefits of using a ORM like nHibernate is productivity, having an object oriented way to manage object persistence. However, not everything is just roses and candles, and nHibernate has mappings. Those tedious xml files we used to write to map a class into an object representation were a real pain in the a**.

However, then we had Fluent nHibernate to save our days and everything went better, we had fluent mappings, which rock much more than the old way of making mappings. For those of you who haven't heard of Fluent nHibernate (FNH from now on) it's a wonderful tool that allows you (amoong other really useful things) to map your data classes to nHibernate and allow you to do stuff like Lazy Loading and many other perks. It supports a great deal of the functions provided by nHibernate but writing pretty classes and not nasty xml. Ok, no more chatty stuff... Let's see a simple example, say you have this class:

public class Product
{
 public virtual int Id {get; set;}
 
 public virtual string Name {get; set;}
 
 public virtual float BasePrice {get; set;}
 
 public virtual Category Category {get; set;}
}
 

This class can be mapped to nHibernate using something called ClassMap provided by the most useful FNH, like this:

public class ProductMapping : ClassMap<Product>
{
 public ProductMapping()
 {
  Id( x => x.Id );
  
  Map( x => x.Name );
  Map( x => x.BasePrice );
  
  References( x => x.Category );
 }
}
 

Ok, this is pretty much it... if you want to read more on Fluent Mappings, be sure to check their wiki which is a pretty good introduction and if you are new to nHibernate I'd have to recommend Jason Dentler's nHibernate 3.0 Cookbook (I know I'm missing a link here...), which is also pretty good.

Now, to the purpouse of the post, in my interest on speeding my development, I started applying those stuff that I learned from Steven Sanderson's about Scaffolding in an awsome serie of blog posts he made.

So, instructions... First thing you need to do is install the MvcScaffolding package. Then you should create your own Scaffolder project. The steps are pretty much like this:

 Install-Package MvcScaffolding
 ....
 Scaffold CustomScaffolder AutoFluentMapper
 

Now you will get a Powershell file and a T4 file. For your Powershell write this:

[T4Scaffolding.Scaffolder(Description = "AutoFluentMapper. Scaffolding mappings")][CmdletBinding()]
param(       
    [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
    [string]$DomainFolder,
    [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
    [string]$BaseEntity,
    [string]$Project,
 [string]$CodeLanguage,
 [string[]]$TemplateFolders,
 [switch]$Force = $false
)

$namespace = (Get-Project $Project).Properties.Item("DefaultNamespace").Value;
$folder = Get-ProjectFolder $DomainFolder;

$entitiesNamespace = $namespace + "." + $DomainFolder;
$mappingsNamespace = $namespace + "." + "Mappings";

if (!$BaseEntity)
{
    $BaseEntity = "System.Object";
}

foreach ( $folderItem in $folder )
{
    $entityName = $folderItem.Name.Replace(".cs", "");
    $entityFullName = $entitiesNamespace + "." + $entityName;
    
    $entity = Get-ProjectType $entityFullName -Project $Project;
    
    if ( !$entity ) { Write-Host "Entity $entityFullName not found!"; return; }
    
    $mappingName = $entityName + "Mapping";
    
    
    Add-ProjectItemViaTemplate -OutputPath "Mappings\$entityName" `
                           -Template "AutoFluentMapperTemplate" `
                           -TemplateFolders $TemplateFolders `
                           -SuccessMessage "Added Mappings output at {0}" `
                           -Model @{ 
                                    ModelType = $entity; EntityName = $entityName;
                                    Namespace = $mappingsNamespace; EntityNamespace = $entitiesNamespace;
                                    BaseEntity = $BaseEntity
                                    }`
                            -Project $Project -CodeLanguage $CodeLanguage -Force:$Force
                            
    
}
 

Ok, I know you don't toss an amount of code like this without a good explanation, so I'll try to explain myself. The first block defines the list of parameters that our Scaffolder will accept, if you take the powershell script that you get when creating a new scaffolder, we only added a DomainFolder, which we will use to search in that folder the domain entities and a BaseEntity to check if we have a normal class or if we have model inheritance. Then, we have a small block of code to manage the names of the namespaces for the mappings, and finally if no base entity name is provided, we'll assume that our base model is System.Object.

After we've done all these small twitches, all we need is to process each file we find in the folder specified by DomainFolder. We get the name of the entity, then the full name considering the namespace, and finally we add the new file, pretty much like this:

$entityName = $folderItem.Name.Replace(".cs", "");
$entityFullName = $entitiesNamespace + "." + $entityName;
$entity = Get-ProjectType $entityFullName -Project $Project;
if ( !$entity ) { Write-Host "Entity $entityFullName not found!"; return; }

Add-ProjectItemViaTemplate -OutputPath "Mappings\$entityName" `
 -Template "AutoFluentMapperTemplate" `
 -TemplateFolders $TemplateFolders `
 -SuccessMessage "Added Mappings output at {0}" `
 -Model @{ 
  ModelType = $entity; EntityName = $entityName;
  Namespace = $mappingsNamespace; EntityNamespace = $entitiesNamespace;
  BaseEntity = $BaseEntity
 }`
 -Project $Project -CodeLanguage $CodeLanguage -Force:$Force
 

Ok, now we need to work our T4 template. If you don't know what T4's are all about, then consider checking out Oleg Sych's post on T4 templates which is the way to start and according to Scott Hanselman is a place with some good resources. Now, what will we do with our T4, we will generate our mapping by inspecting into the code's properties.

This is the part were I'll have to apologize, because when I generate my models I include an Id property, which is an int, so I didn't inspected for that property and just added hard coding it, feel free to make changes and do tell me of new ideas...

The T4 template I built is based on one idea, take a model, check if it inherits from something other than the base model, inspect the properties and build the mappings accordingly:

var modelType = (CodeClass)Model.ModelType;
 
var baseEntityName = (string)Model.BaseEntity;
var parentEntities = (CodeElements)modelType.Bases;

var derivedFromBase = false;

foreach( CodeElement parentEntity in parentEntities ){
 if ( parentEntity.Name.Contains(baseEntityName))
  derivedFromBase = true;
}

var inheritable = derivedFromBase ? "ClassMap" : "SubclassMap";

var properties = new List<CodeProperty>();
var enumerables = new List<CodeProperty>();
var references = new List<CodeProperty>();
 

So, the idea was to separate the properties (or attributes of the domain entities) in 3 groups:

  • Properties: Those attributes that have a basic type, say strings, integers, DateTimes, and stuff like that.
  • Enumerables: Those attributes that define an IEnumerable collection, most commonly seen on many to one or many to many (not supported yet, sorry...) relationships
  • References: Thos attributes that reference another attribute of our domain

Having these ideas in mind, and will a little of T4 and EnvDTE magic we can get a fully fledged template for our Scaffolding needs. All we need now is to run it, much like this:

Scaffold AutoFluentMapper Entities -BaseEntity BaseDomainEntity
 

For those interested only in the how it works idea, the Entities argument defines the name of the folder where I have my entities, and their namespace is infered from there (convention over configuration..). The -BaseEntity parameter is used to determine the base of your domain models to check for class inheritance in nHibernate models, if you ommit this argument, I'll assume it's System.Object. Besides these 2 arguments, this is just a regular plugin and I've tried it already. Hope you enjoy it!

If you are using this code as part of your projects, send me an email or leave a comment to know that I've actually helped somebody, which is the main thing that encourages me to write more of this stuff. The Source code can be downloaded here as a zip file.

2 comments:

  1. David

    This is WAY easier to use than NHibernate:
    https://www.kellermansoftware.com/p-47-net-data-access-layer.aspx

    ReplyDelete
  2. A - I've never heard of that (not sure on how good it could be)
    B - If you don't like NH, you can always use Entity Framework or Linq 2 Sql (Both free, but not OS)
    B - Is not free!!! Actually, for a world where you can get quality ORM for free, it's ridiculously expensive!

    ReplyDelete

Commenting is allowed!