Fluent NHibernate Automapping

Quick intro to Fluent NHibernate, resource links, and a working downloadable self-contained sample. Covers only the basics of using AutoMapping, schema generation, and basic NHibernate CRUD actions.

 

I've been following NHibernate and, more recently, the Fluent NHibernate offering for a while now. I have a book on it (NHibernate in Action), which I've now gone through a couple of times, and I believe I have mastered the basics of using NHibernate. I like NHibernate for a few reasons:

1) It is a mature ORM originally ported from the JAVA (Hibernate) space, and is quite feature - complete.

2) It has a large user base, and plenty of places to get help.

3) It is one of the easiest ways to support "top down" (POCO  first) Development, which can offer numerous advantages over the data-centric "Database First" paradigm that we .NET Developers have been taught since back in the Windows DNA days of ADO and ADO.NET. You cannot really do true top-down development with LINQ to SQL or Entity Framework, although these are certainly good tools and I still use them.

4) The Fluent NHibernate tool makes it orders of magnitude easier to create mappings and database schema from your POCO's so that you can focus on Domain Driven Development and build quality, best-practices objects.

5) You can develop data objects  with "Persistence Ignorance" - meaning that your classes will not require being glopped up with mapping attributes, special design requirements, external references, or related mapping files. That's right - your objects will have no knowlege of how or even whether they are being persisted somewhere.

 Of course, there are plenty of other good reasons to look at NHibernate, but to me the above are the most relevant ones.

To his credit, James Gregory has been the driving force on the Fluent NHibernate offering, and he's done a great job. Not only is this guy smart and a gentleman, he listens to what others have to say, no matter how stupid it may seem. James does Fluent like I smoke cigars - with gusto!

The concept behind this type of Fluent interface is to take the tedium out of otherwise complex or  difficult jobs (like creating XML mapping files) in an intuitive, simple way, emphasizing Convention over Configuration. What this means is that when you use Fluent NHibernate to generate your mapping and persistence configuration, it will proceed with "sensible defaults" that make assumptions that will be valid in a majority of cases. For example, if it sees a POCO class with an ID property, it will assume this is the Primary Key in the persistence layer. You can override many defaults, such as naming conventions, with utlity methods that are provided in the Fluent interface. The example I present here makes use of the "100% Fluent" methods.  This allows us to have a mapping generator method that looks as simple as this:

public static Configuration GenerateMapping(string connectionString)
        {
            var config = MsSqlConfiguration.MsSql2005
                .ConnectionString(c => c.Is(connectionString))
                 .ConfigureProperties(new Configuration());
             AutoPersistenceModel.MapEntitiesFromAssemblyOf<Notes.Note>().Configure(config);
            return config;
        }

"Notes" in the above is the assembly that contains my POCO types. The use of a Fluent interface means that every method returns an instance of the type, permitting us to simply type a dot "." and use Intellisense to choose the next method we want to execute. Liberal use of inline lambdas keeps the code compact and easy to understand. You can also change the database to any of a number of flavors including Oracle, SQLite, etc. by simply changing the Fluent "MsSqlConfiguration" provider to another one. NHibernate takes care of all the RDBMS-specific syntax. That's right - it's database-agnostic.  To "apply" this mapping and actually generate the database schema (and optionally execute it) all we need is this:

new SchemaExport(config).SetOutputFile(@"C:\temp\Notes.sql").Create(true, true);

That's it - we are ready to begin using NHibernate.  We have a database schema that matches our classes, and it's ready to go. And the nicest thing about this is that we can do our Domain Driven Design in the POCO's where it belongs. I believe this is an important concept to master, since as .NET developers we've mostly been taught to do our design work starting from the database. As a result, we don't practice "lean programming" and tend to over-engineer everything at the start. With DDD and Lean, you only create what is really needed when you need it. Change, add or improve something in the design? No problem, just execute the SchemaExport again and your database is updated to correctly reflect your domain model.

There's no space to get into it here, but once you've got your configuration and your schema set up, NHibernate itself brings to the table a very sophisticated API that is feature - complete, mature, and truly object - oriented in nature. With a little effort, you will begin to see the real power that NHibernate provides to you as an OOP developer. You can implement helper classes that enable you to use NHibernate in ASP.NET projects, and you can even implement direct Sql queries where appropriate, returning, updating and storing  real objects that sync with your domain model. The API syntax is intuitive, useful, and productive.

This is what my POCO's look like for this example:

Note Class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Notes
{
    public class Note
    {
         public virtual Guid Id { get; set; }
        public virtual string Subject { get; set; }
        public virtual string NoteText { get; set; }
        public virtual DateTime DateEntered { get; set; }
         public virtual Category Category { get; set; }      
         public virtual User User { get; set; }
    }
}

Category Class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Notes
{
  public  class Category
    {
      public virtual int Id { get; set; }
      public virtual string CategoryName { get; set; }
      
     }
}

User Class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Notes
{
   public class User
    {
       public virtual int Id { get; set; }
       public virtual string UserName { get; set; }
       public virtual string Passphrase { get; set; }
    }
}

Notice that properties  are defined as virtual, which NHibernate requires. This should not be a problem since that's good design anyway. Also notice that in the Note class I have defined my Category and User properties as full-fledged objects. NHibernate will take care of the foreign keys in the database when it reflects and creates my schema DDL. Take notice that my POCO classes are "clean" - devoid of any external reference or mapping attributes. They can also be marked Serializable.

Now here is a Console app that "exercises" some of this, just so we can look at some NHibernate basics:

static void Main(string[] args)
        {
             string connectionString = sysConfig.ConfigurationManager.ConnectionStrings["connectionString"].ConnectionString;
         var config = GenerateMapping(connectionString);
           // uncomment next line to create and execute db schema in database and show the Sql  to the console:
         // new SchemaExport(config).SetOutputFile(@"C:\temp\Notes.sql").Create(true, true);
            var sessionFactory = Fluently.Configure()  
            .Database(MsSqlConfiguration.MsSql2005  
            .ConnectionString(c => c
            .Is(connectionString)))  
            .Mappings(m =>m.AutoMappings.Add(  
                 AutoPersistenceModel.MapEntitiesFromAssemblyOf<Notes.Note>()
                 )).BuildSessionFactory();    

            var session = sessionFactory.OpenSession();
               ITransaction transaction = session.BeginTransaction();
             // CREATE NEW NOTE: create and save category and user first as they have FK to Note object:
            Category ca = new Category() { Id = 1, CategoryName = "Testing" };
            session.Save(ca);
            Category ca2 = new Category() { Id = 2, CategoryName = "Personal" };
            session.Save(ca2);
            Category ca3 = new Category() { Id = 3, CategoryName = "Business" };
            session.Save(ca3);
          
            User u = new User() { Id = 1, Passphrase = "babbalooie", UserName = "Peter" };
            session.Save(u);

            // GET ALL USERS INTO List<User>:
            List<User> users = new List<User>();
                   try
            {
                users = (List<User>)session.CreateCriteria(typeof(User)).List<User>();
             }
             catch (HibernateException)
             {
                 if (null != transaction)
                 {
                      transaction.Rollback();
                     Console.WriteLine(" NO USERS.");
                }

            }
                 Console.WriteLine("Got " + users.Count.ToString() + " users.");
            
          Notes.Note note = new Note()
          {
              Category = ca,
              User = u,
              DateEntered = DateTime.Now,
              Id = Guid.NewGuid(),
              NoteText = "This is some note text!",
              Subject = "The subject"                
          };
          session.Save(note);
          transaction.Commit();
          // RETRIEVE SAVED NOTE BY ID:
          ITransaction transaction2 = session.BeginTransaction();
          Note note2 = (Note)session.Get(typeof(Note), note.Id); // get the note we just saved
          Console.WriteLine("ID=" + note2.Id);
          Console.WriteLine("Subject=" + note2.Subject);
          Console.WriteLine("NoteText=" + note2.NoteText );          
          Console.WriteLine("Category=" + note2.Category.CategoryName +" ID=" + note2.Category.Id);
           Console.WriteLine("User=" + note2.User.UserName);
          transaction2.Commit();

         // MODIFY AND SAVE A STORED NOTE

          ITransaction transaction3 = session.BeginTransaction();
          Note note3 = (Note)session.Get(typeof(Note), note.Id); // get the first note we  saved
          note3.Category.CategoryName = "Changed Category";
          note3.Subject = "Changed Subject";
          Console.WriteLine("ID=" + note3.Id);
          Console.WriteLine("Subject=" + note3.Subject);
          Console.WriteLine("NoteText=" + note3.NoteText);
           Console.WriteLine("Category=" + note3.Category.CategoryName + " ID=" + note3.Category.Id);
           Console.WriteLine("User=" + note3.User.UserName);
           session.Update(note3);
           transaction3.Commit();


           Console.ReadLine();
         }
If you are just starting out with Fluent NHibernate, you can download the example project and play with it. All assemblies required are referenced and located in the /bin folder. If you really want to grok this stuff, I recommend svn downloading source for NHibernate and for Fluent NHibernate - and then you can single step through the code and really get dirty under the hood. Hey! It's green eggs and ham time --Let's throw away some of those old, worn out ideas and try a new approach to development!
By Peter Bromberg   Popularity  (9834 Views)