This is the third installment in my series. In part 1, we downloaded our libraries and set up our solution. In part 2, we built our model. In this part, we’ll configure NHibernate and set up our database mappings. We’ll also set up our database schema.
Before we can talk about Fluent NHibernate, you need to know a little bit about setting up mappings in plain old NHibernate. In a typical NHibernate setup, you’ll have a bunch of mapping files like this:
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"> <class name="NStackExample.Address, NStackExample.Core" table="Address"> <composite-id> <key-many-to-one name="Person" class="NStackExample.Person, NStackExample.Core" column="ID" /> <key-property name="Type" type="Int32" /> </composite-id> <property name="City" type="String" length="255" /> <property name="Lines" type="String" length="255" /> <property name="State" type="String" length="2" /> <property name="Zip" type="String" length="10" /> </class> </hibernate-mapping>
You’ll have one of those for each of your entities. It’s left over from Java’s Hibernate project, and in my opinion, It’s a royal pain, complete with ruby scepter. Lucky for you, there’s a better way™.
With Fluent NHibernate, the mapping file above can be expressed using this class instead:
Imports FluentNHibernate.Mapping Public Class AddressMapping Inherits ClassMap(Of Address) Public Sub New() UseCompositeId _ .WithKeyReference(Function(x As Address) x.Person) _ .WithKeyProperty(Function(x As Address) x.Type) Map(Function(x As Address) x.Lines).WithLengthOf(255) Map(Function(x As Address) x.City).WithLengthOf(255) Map(Function(x As Address) x.State).WithLengthOf(2) Map(Function(x As Address) x.Zip).WithLengthOf(5) End Sub End Class
using FluentNHibernate.Mapping; namespace NStackExample.Data { public class AddressMapping : ClassMap<Address> { public AddressMapping() { UseCompositeId() .WithKeyReference(x => x.Person) .WithKeyProperty(x => x.Type); Map(x => x.Lines).WithLengthOf(255); Map(x => x.City).WithLengthOf(255); Map(x => x.State).WithLengthOf(2); Map(x => x.Zip).WithLengthOf(5); } } }
It may look even more complicated than the XML mapping, but with Intellisense, it’s a breeze. Plus, there are no magic strings to worry about. When you change a property name using a refactor tool, your mapping won’t be left out of sync.
Now that you have the basic idea, let’s get back on track.
Since the database connection, NHibernate configuration, entity mappings, and DAO implementations are really just implementation details of our chosen ORM, they should go in a separate assembly.
Now, let’s make our mappings.
Imports FluentNHibernate.Mapping Public Class CourseMapping Inherits ClassMap(Of Course) Public Sub New() Id(Function(x As Course) x.ID).GeneratedBy.GuidComb() Map(Function(x As Course) x.Subject).Not.Nullable.WithLengthOf(4).UniqueKey("CourseNaturalKey") Map(Function(x As Course) x.CourseNumber).Not.Nullable.WithLengthOf(4).UniqueKey("CourseNaturalKey") Map(Function(x As Course) x.Title).Not.Nullable.WithLengthOf(255) Map(Function(x As Course) x.Description).Not.Nullable.WithLengthOf(1024) Map(Function(x As Course) x.Hours).Not.Nullable() HasMany(Function(x As Course) x.Sections) _ .AsSet() _ .WithForeignKeyConstraintName("CourseSections") End Sub End Class
using NStackExample; using FluentNHibernate.Mapping; namespace NStackExample.Data { public class CourseMapping : ClassMap<Course> { public CourseMapping() { Id(x => x.ID).GeneratedBy.GuidComb(); Map(x => x.CourseNumber) .Not.Nullable() .WithLengthOf(4) .UniqueKey("CourseNaturalKey"); Map(x => x.Subject) .Not.Nullable() .WithLengthOf(4) .UniqueKey("CourseNaturalKey"); Map(x => x.Title) .Not.Nullable() .WithLengthOf(255); Map(x => x.Description) .Not.Nullable() .WithLengthOf(1024); Map(x => x.Hours) .Not.Nullable(); HasMany(x => x.Sections) .AsSet() .WithForeignKeyConstraintName("CourseSections"); } } }
Most of this is self-explanatory and works exactly like you would expect.
Our mapping class inherits from ClassMap(Of Course). ClassMap is the specific type that Fluent NHibernate searches for when looking for mappings. In this case, it signifies that this class provides the mapping for our Course entity. In the constructor, we define our specific mapping for each property.
By default, all relationships are lazy-loaded. This means that when you fetch a course from the database, the associated sections aren’t fetched right away. It works just like you would expect: They aren’t fetched until you access the Sections property. If you never access the Sections property, those sections are never fetched from the database, which can greatly improve performance. This is all made possible with proxies, but that’s another series of posts.
Now let’s map the sections:
Imports FluentNHibernate.Mapping Public Class SectionMapping Inherits ClassMap(Of Section) Public Sub New() Id(Function(x As Section) x.ID).GeneratedBy.GuidComb() Map(Function(x As Section) x.FacultyName).WithLengthOf(255) Map(Function(x As Section) x.RoomNumber).WithLengthOf(10) Map(Function(x As Section) x.SectionNumber) _ .WithLengthOf(4) _ .Not.Nullable() _ .UniqueKey("SectionNaturalKey") References(Function(x As Section) x.Course) _ .Not.Nullable() _ .UniqueKey("SectionNaturalKey") References(Function(x As Section) x.Term) _ .Not.Nullable() _ .UniqueKey("SectionNaturalKey") HasMany(Function(x As Section) x.StudentSections) _ .AsSet() _ .WithForeignKeyConstraintName("SectionStudentSections") End Sub End Class
The References function maps the Many-to-one relationship. Think of it as the other side of our one-to-many relationship. It is the reference from the child – section - back to it’s parent - course.
For homework, finish mapping all of the entities.
I bet you’re thinking this post is getting long considering we haven’t even started building the database. Well don’t worry. NHibernate will do that for us.
Before I discovered NHibernate, I would spend at least a day setting up my database. It was insane. It drove me insane. I bet it drives you insane. It ends today.
Disclaimer: If you are trying to use an existing shared legacy database, the chances of your existing DB schema working without some tweaking are slim. This post by Fabio Maulo explains your options.
First, let’s configure NHibernate. The Fluent NHibernate Wiki has a great page explaining the fluent configuration of NHibernate.
Imports NHibernate Imports NHibernate.Tool.hbm2ddl Imports FluentNHibernate.Cfg Imports System.Configuration Imports System.IO Public Class Configuration Private m_SchemaPath As String Private m_Factory As ISessionFactory Public Function Configure() As Configuration m_SchemaPath = ConfigurationManager.AppSettings("NStackExample.Data.Configuration.SchemaPath") m_Factory = Fluently.Configure _ .Database(Db.MsSqlConfiguration.MsSql2005 _ .ConnectionString(Function(x As Db.MsSqlConnectionStringBuilder) _ x.FromConnectionStringWithKey("NStackExample.Data.Configuration.DB"))) _ .Mappings(Function(x As MappingConfiguration) _ x.FluentMappings.AddFromAssemblyOf(Of CourseMapping)() _ .ExportTo(m_SchemaPath)) _ .ExposeConfiguration(AddressOf BuildSchema) _ .BuildSessionFactory() Return Me End Function Private Sub BuildSchema(ByVal Cfg As NHibernate.Cfg.Configuration) Dim SchemaExporter As New NHibernate.Tool.hbm2ddl.SchemaExport(Cfg) SchemaExporter.SetOutputFile(Path.Combine(m_SchemaPath, "schema.sql")) SchemaExporter.Create(False, True) End Sub Public Function OpenSession() As ISession If m_Factory Is Nothing Then Configure() Return m_Factory.OpenSession End Function End Class
using FluentNHibernate.Cfg; using FluentNHibernate.Cfg.Db; using NHibernate; using NHibernate.Cfg; using NHibernate.Tool.hbm2ddl; using System.IO; using System.Configuration; namespace NStackExample.Data { public class Configuration { private ISessionFactory m_Factory; private string m_SchemaPath; public Configuration Configure() { m_SchemaPath = ConfigurationManager.AppSettings["NStackExample.Data.Configuration.SchemaPath"]; m_Factory = Fluently.Configure() .Database(MsSqlConfiguration.MsSql2005 .ConnectionString( x => x.FromConnectionStringWithKey("NStackExample.Data.Configuration.Db"))) .Mappings(x => x.FluentMappings.AddFromAssemblyOf<CourseMapping>() .ExportTo(m_SchemaPath)) .ExposeConfiguration(BuildSchema) .BuildSessionFactory(); return this; } private void BuildSchema(NHibernate.Cfg.Configuration cfg) { SchemaExport SchemaExporter = new SchemaExport(cfg); SchemaExporter.SetOutputFile(Path.Combine(m_SchemaPath, "schema.sql")); SchemaExporter.Create(true, false); } public ISession OpenSession() { if (m_Factory == null) Configure(); return m_Factory.OpenSession(); } } }
The configuration falls in to two sections: Database and Mappings. In our case, the database is SQL 2005 and the connection string is read from a connection string element in the web.config. All of the mappings are fluent, not auto-mapped. Notice that we are exporting our mappings to a directory specified in the appsettings section of the web.config. This will convert our fluent mappings to individual hbm.xml files. This is great for debugging the mappings, especially when asking for NHibernate help online.
We have one additional item. We’re using the ExposeConfiguration method to call our BuildSchema function, passing in our complete NHibernate configuration.
In BuildSchema, we use a great hidden tool in NHibernate: the schema export. This amazing class will build your database for you. The create function takes two boolean parameters. The first specifies if the schema should be written out to a ddl file – a database script to build all of the tables, keys, indexes, and relationships in your database. The second boolean parameter specifies if the script should be executed against the specified database.
It’s that easy.
Two warnings:
One last note: As with any project, you will have to adapt as you build. These mappings are not exactly what we use in the final build. I can guarantee our model will change significantly. I will take you through those changes as they happen, and explain the reasons behind them.
I’ve decided not to post the complete source code at this stage. Instead, I leave the remaining mappings as an exercise for you, the reader. They will be included in the next source release.
In the next post, I’ll show you how to test your mappings – including querying, reading from and writing to the database.
Jason
- Mapped out. Good night.
P.S. – Special thanks to Tuna, Fabio, and Oren for the feedback, answers to stupid questions, and great advice!
For the NHForge readers, I apologize for the lack of code formatting. Now that I know the software, it should improve starting in the next post.