This post is going to go over Automapping using Fluent NHibernate with regards to my blogging engine. If you’re unfamiliar with the concept of automapping, you should browse James Gregory’s introductory post. Basically, we gained the benefit of XML-less configuration when mapping our data transfer objects (DTO) to database tables and columns with Fluent NHibernate over plain old NHibernate. Mapping was achieved through “mapping” classes, one per DTO, so if you had a “User” class, you’d also need a “UserMap” class that told FluentNHibernate how to map each property in your class to the column in a database table. Automapping removes the requirement of creating mapping classes and relies on conventions over configuration. Here we go.
First off, in order to get automapping to work, you’ll need to generate an AutoPersistenceModel object that encompasses your automapping parameters. I’ll post the code and then I’ll go through it step by step.
public AutoPersistenceModel GetPersistenceModel()
{
return AutoPersistenceModel.MapEntitiesFromAssemblyOf<Blog>()
.WithSetup(a => a.IsBaseType = type => type == typeof (DomainEntity))
.Where(type =>
type.Namespace.EndsWith("Domain.Model") &&
!type.IsAbstract &&
type.IsClass &&
type.GetProperty("Id") != null)
.ConventionDiscovery.AddFromAssemblyOf<IdConvention>();
}
As you can see, this class has only one responsibility at the end of the day, to generate an AutoPersistenceModel. The AutoPersistenceModel class is the heart of the automapping configuration. Let’s go through it.
return AutoPersistenceModel.MapEntitiesFromAssemblyOf<Blog>()
This line lets FluentNHibernate’s automapping framework know where to find the DTOs. The framework will look through the assembly and attempt to map each class to a table in the database. How does it know not to map a particular class, such as a base class? Read on
.WithSetup(a => a.IsBaseType = type => type == typeof (DomainEntity))
This line lets the automapping framework “know’ that DomainEntity is a base type, and not a type or class that needs to be mapped to a table in the database.
.Where(type =>
type.Namespace.EndsWith("Domain.Model") &&
!type.IsAbstract &&
type.IsClass &&
type.GetProperty("Id") != null)
Here’s where it gets a little more interesting. The above code is where we tell the framework exactly which classes we’d like to map to a database table. We first let it know that the class should be in the Domain.Model namespace, should not be abstract, should be a class, and that there should be a property named “Id” that should not be null.
.ConventionDiscovery.AddFromAssemblyOf<IdConvention>();
A simplistic line of code, but it actually does quite a bit as we’ll soon find out. Now that we’ve told Fluent NHibernate Automapping how to find DTOs to map to database tables, we now would like to tell it what to do with them by defining some conventions. See James Gregory’s quick overview to the convention framework by visiting his blog.
To see why I have the above line of code, maybe it would make sense to see inside of my “Core” solution and its architecture/topology:
I just want to focus in on the Conventions folder/namespace and go through each of those convention classes to see what is going on in each, and why they are needed.
public class HasManyConvention : IHasManyConvention
{
public bool Accept(IOneToManyPart target)
{
return true;
}
public void Apply(IOneToManyPart target)
{
Type t = target.GetType();
if(t == typeof(OneToManyPart<Blog>))
target.KeyColumnNames.Add("OwnerId");
if(t == typeof(OneToManyPart<Post>))
target.KeyColumnNames.Add("AuthorId");
target.Cascade.All();
}
}
This class is responsible for handling the HasMany convention, which is used for all OneToMany relationship mappings. In our case, we have a few of these mappings:
- User has many Blogs (via User’s property IList<Blog> Blogs)
- User has many Posts (via User’s property IList<Post> Posts)
- Blog has many Posts (via Blog’s property IList<Post> Posts)
As you can see the Accept() function simply returns true, which in turn is letting the automapping framework know to apply this convention to all classes being mapped. And what is being applied? Well everything happening inside of the Apply() function, of course
First off, I’m getting the type of the target, which is of type IOneToManyPart. The target parameter is automagically in the form of OneToManyPart<Blog> or OneToManyPart<Post> depending on which OneToMany relationship is being mapped (User.Blogs, User.Posts or Blog.Posts). Now that I have the type of OneToMany relationship, I compare it to the current target’s mapping.
If the type is OneToMany<Blog>, then I need to tell the FluentNHibernate Automapping framework to use column “OwnerId”. This may seem weird, but maybe this SQL query generated by NHibernate will help a little. The code is getting a User with Id 1, and returning all the Blogs owned by that user.
NHibernate: SELECT user0_.Id as Id1_0_, user0_.Password as Password1_0_, user0_.Email as Email1_0_ FROM "User" user0_ WHERE user0_.Id=@p0; @p0 = '1' NHibernate: SELECT blogs0_.OwnerId as OwnerId1_, blogs0_.Id as Id1_, blogs0_.Id as Id0_0_, blogs0_.LastUpdated as LastUpda2_0_0_, blogs0_.Title as Title0_0_, blogs0_.Description as Descript4_0_0_, blogs0_.OwnerId as OwnerId0_0_ FROM "Blog" blogs0_ WHERE blogs0_.OwnerId=@p0; @p0 = '1'
If I did not tell FluentNHibernate to use the column “OwnerId”, it would instead use “User_Id” by convention, since in the Blog class, Owner is of type User. I do not want this convention, so I must override it in order for the mapping to work successfully. Yes, it’s more work for me to not use the convention, but, I wanted to illustrate the point.
The same idea goes for a target of type OneToMany<Post>. I tell the framework to use column “AuthorId” instead of it’s normal “User_Id” convention.
The last convention added to a OneToMany relationship is how to cascade updates/deletes/etc, by letting the framework know to cascade all. So now when I delete an object through NHibernate, NHibernate will run a SQL query to cascade the delete properly.
Let’s move on to the next convention:
public class HasManyToManyConvention : IHasManyToManyConvention
{
public bool Accept(IManyToManyPart target)
{
return true;
}
public void Apply(IManyToManyPart target)
{
target.Cascade.All();
}
}
This convention is much simpler as it just states that for every ManyToMany relationship, I only want the cascade all behavior. The same goes for a HasOne relationship, but for full-disclosure’s sake, I’ll post the code below:
public class HasOneConvention : IHasOneConvention
{
public bool Accept(IOneToOnePart target)
{
return true;
}
public void Apply(IOneToOnePart target)
{
target.Cascade.All();
}
}
Another simple, but important convention to my database model, is my IdConvention:
public class IdConvention : IIdConvention
{
public bool Accept(IIdentityPart target)
{
return true;
}
public void Apply(IIdentityPart target)
{
target.ColumnName("Id");
}
}
I believe that by convetion, FluentNHibernate’s AutoMapping framework looks for the ID column in a database by using the DTO class name and appending “Id” to it. I do not want this behavior, so I’m letting the framework know that I want it to know that when looking for the identity column in the database for each table, look for a column named “Id”.
The last custom convention that I laid out was the ManyToOne relationship convention.
public class ReferencesConvention : IReferenceConvention
{
public bool Accept(IManyToOnePart target)
{
return true;
}
public void Apply(IManyToOnePart target)
{
target.ColumnName(target.Property.Name + "Id");
}
}
Again, I’m apply this convention to all mapped classes that have a ManyToOne relationship and then I’m letting the framework know that the column names will be in the format of Property name + “Id”. An example of a ManyToOne relationship in my database model is between Blogs and Users. A blog has an Owner property (which is a user), so to map this relationship, we’re telling FluentNHibernate to look for a column named “OwnerId” in the Blog table. And that does it for my conventions.
Alright, let’s figure out where we are right now.
- I’ve got a class, BlogAutoPersistenceModelConfig, that creates an AutoPersistenceModel object
- The class also let’s the FluentNHibernate framework know that each of my DTOs have a base class of DomainEntity
- The class also adds some criteria to the framework to let it know which DTOs to map, and which ones to ignore.
- The last step is to add conventions to the mapped classes to determine how to handle different relationship types, and where to find the ID column in a table.
Now that we’ve got an AutoPersistenceModel configured, we can proceed to create an ISessionFactory as part of the NHibernate framework. That’ll be a topic for the next post, and it should be quite a bit shorter.
Hope this was a little helpful in your automapping ventures.