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! |