NHibernate Start(转)
What is NHibernate
NHibernate is a .NET based object persistence library for relational databases. NHibernate is a port of the excellent Java Hibernate relational persistence tool.
NHibernate handles persisting your .NET objects to and from an underlying relational database. Rather than you having to write SQL to get your objects in and out of the database, NHibernate takes care of this for you. Your code only need be concerned with your objects, NHibernate generates the SQL and makes sure that things end up in the correct tables and columns.
Why this Guide?
Anybody who is at all familiar with Hibernate will recognize this as being really similar to Glen Smith's A Hitchhiker's Guide to Hibernate. The content is based on that guide so all of the credit needs to be given to him.
NHibernate's documentation is not anywhere near the point that Hibernate's documentation is. However, the project's are similar enough that the reader should be able to look at Hibernate's documentation and get a very good feel for how NHibernate works.
This doc is intended to get you up and running with NHibernate as quickly as possible. It will cover how to persist a simple object to a table. For more complex examples please see the NUnit test that come with the code.
The Development Process
NHibernate is going to have some tools to help you generate a schema (in codebase now), generate classes from mapping files (on the drawing board), and to update a schema (suggestion from a new developer). However, for this example we are going to assume everything from setting up the table to coding the .NET class is done manually. Here are the steps we are going to perform:
- Create the table to persist the .NET Class to.
- Create a .NET Class that needs to be persisted.
- Create a mapping file so NHibernate knows how to persist the .NET Class' Properties
- Create a configuration file for NHibernate to know how to connect to your Database
- Use the NHibernate API
Step 1: Writing the SQL
The example we're working through is a very simple one. Consider you've developing a basic user management subsystem for your website. We'll use a Users table that looks like this (I am assuming you already have a database setup - in my case I am calling it NHibernate):
use NHibernate go CREATE TABLE users ( LogonID varchar(20) NOT NULL default '0', Name varchar(40) default NULL, Password varchar(20) default NULL, EmailAddress varchar(40) default NULL, LastLogon datetime default NULL, PRIMARY KEY (LogonID) ) go
I'm using MS Sql Server 2000, but feel free to use any kind of database you've got a .NET Data Provider driver for. We've got a User table with a Logon ID, Full Name, Password, Email and date of last logon. Pretty standard sort of deal. Next step is to write a .NET Class to handle a given User
Step 2: Creating the .NET Class
When we get a bunch of Users in memory, we'll need some sort of object to hold them for us. NHibernate works via reflection on Properties of the objects, so we'll need to add Properties to the Class we want to persist. A simple Class that can be persisted with NHibernate looks like :
using System; namespace NHibernate.Demo.QuickStart { public class User { private string id; private string userName; private string password; private string emailAddress; private DateTime lastLogon; public User() { } public string Id { get { return id; } set { id = value; } } public string UserName { get { return userName; } set { userName = value; } } public string Password { get { return password; } set { password = value; } } public string EmailAddress { get { return emailAddress; } set { emailAddress = value; } } public DateTime LastLogon { get { return lastLogon; } set { lastLogon = value; } } } }
In our example above, we've made the Properties and the Constructor public - but that's not a requirement for NHibernate - it can use public, protected, internal, or even private Properties to persist your data.
Step 3: Writing the Mapping File
So we've now got our SQL table, and the .NET Class that's going to map to it. We still need a way to tell NHibernate how to map from one to the other. This is accomplished via a mapping file. The cleanest (most maintainable) way is to write one mapping file per Class, and if you name it YourObject.hbm.xml
and put it in the same directory as your Class, NHibernate will make things even easier for you. Here's an example of what User.hbm.xml
might look like:
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.0"> <class name="NHibernate.Demo.QuickStart.User, NHibernate.Demo.QuickStart" table="users"> <id name="Id" column="LogonId" type="String(20)"> <generator class="assigned" /> </id> <property name="UserName" column="Name" type="String(40)"/> <property name="Password" type="String(20)"/> <property name="EmailAddress" type="String(40)"/> <property name="LastLogon" type="DateTime"/> </class> </hibernate-mapping>
Let's have a look at some lines of interest in our file. The first tag of interest is the class
tag. Here we map from the Type name (Class name and Assembly) to the users table in our database. This is slightly different than Hibernate. You have to tell NHibernate where to load the Class from. In this case we are loading the Class NHibernate.Demo.QuickStart.User
in the Assembly NHibernate.Demo.QuickStart
. NHibernate follows the same rules of loading a Type as the .NET Framework - so if you have any confusion about how to specify the Type see the .NET Framework SDK.
Let's skip the id
tag for a moment and talk about the property
tags. A cursory scan will show this is where the work is being done. The name
attribute is the Property on our .NET Class, and the column
is the name of the field in our database. The type
attribute is optional (NHibernate will use reflection for a best-guess if you leave it out) - but highly recommended. Users of Hibernate will notice an immediate difference. In the type a length for the String is being specified. This is needed in NHibernate because ADO.NET requires length and precision/scale IDbParameters to have those properties set before the IDbCommand can be prepared.
Ok. Let's return to that id
tag. You may have guessed that this tag has something to do with mapping to the primary key of the table. You'd be right. The form of the ID tag is very similar to the property tags we just looked at. We map from the Property (name
) to the target database field (column
).
The embedded generator tag tells NHibernate how it should produce the primary key (it's quite happy to generate one for you, of whatever type you prefer, but you'll need to tell it how). In our case, we set it to assigned, meaning that our object is going to generate its own keys (the User object will always need to have a UserID after all). If you're keen to let NHibernate generate keys for you, you'll be interested in class settings like uuid.hex
and uuid.string
(check out the docs for more info).
TIP: If you are using Visual Studio .NET to compile make sure that you set the Build Action
of the User.hbm.xml file to Embedded Resource
. The mapping file will now be a part of the Asssembly. The subtle detail's importance will be evident later.
Step 4: Creating a Configuration File for Your Database
We still haven't told NHibernate where to find our database. The most straightforward way is to feed NHibernate a configuration section from your application's config file. Here's what one might look like:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="nhibernate" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.3300.0,Culture=neutral, PublicKeyToken=b77a5c561934e089" /> </configSections> <nhibernate> <add key="hibernate.connection.provider" value="NHibernate.Connection.DriverConnectionProvider" /> <add key="hibernate.dialect" value="NHibernate.Dialect.MsSql2000Dialect" /> <add key="hibernate.connection.driver_class" value="NHibernate.Driver.SqlClientDriver" /> <add key="hibernate.connection.connection_string" value="Server=localhost;initial catalog=nhibernate;User ID=someuser;Password=somepwd;Min Pool Size=2" /> </nhibernate> </configuration>
The example above uses a SqlClient driver, connects to the nhibernate database on localhost, and uses the supplied username and password. There are a bunch of other properties you can set to "tune" how NHibernate accesses your database of choice. Once again, checkout the doco for detailed info.
Please note that the above configuration file does not have any information about configuring log4net. NHibernate uses log4net to log what is happening internally. In a production application I would recommend configuring log4net and setting NHibernate to the appropriate log level for your scenario.
Step 5: Start doing Magic with NHibernate
All the hard work has now been done. If all has gone well so far, you'll have the following:
User.cs
- your C# Class to persistUser.hbm.xml
- your NHibernate mapping fileapp.config
- your configuration settings with ADO.NET connection info (you can do this in code if you like)- A SQL
User
table in your database
Making use of NHibernate in your source is pretty straightforward. The nutshell edition goes:
- Create a Configuration object
- Tell the Configuration about the type of objects you want to store
- Create a Session to your database of choice
- Load, Save and Query your objects
Flush()
your Session back to the database
Let's have a look at some source to make it all much clearer.
First, Create a Configuration Object...
The Configuration object has knowledge of all the mappings that are going on between .NET Classes and the backend database.
Configuration cfg = new Configuration(); cfg.AddAssembly("NHibernate.Demo.QuickStart");
The Configuration object will look through the Assembly for any files ending in .hbm.xml
. There are other ways to add the Mapping files, but this is probably the easiest.
Then, Create a Session Object...
The ISession object represents a connection to your backend database and the ITransaction represents a NHibernate managed Transaction.
ISessionFactory factory = cfg.BuildSessionFactory(); ISession session = factory.OpenSession(); ITransaction transaction = session.BeginTransaction();
Then Load, Save and Query your Objects!
Now you just use your objects in a .NET-native way. Would you like to store a new User in the database? Try something like this:
User newUser = new User(); newUser.Id = "joe_cool"; newUser.UserName = "Joseph Cool"; newUser.Password = "abc123"; newUser.EmailAddress = "joe@cool.com"; newUser.LastLogon = DateTime.Now; // Tell NHibernate that this object should be saved session.Save(newUser); // commit all of the changes to the DB and close the ISession transaction.Commit(); session.Close();
As you can see, the great thing about NHibernate is the low overhead. Go ahead and query your database and verify that you see the new record in the users table. For the most part you just worry about your business objects, and tell NHibernate when you're done.
Let's say you want to retrieve an object when you know the user ID (eg. During a login process to your site). Once a session is opened it's a one-liner; pass in the key and you're done:
// open another session to retrieve the just inserted user session = factory.OpenSession(); User joeCool = (User)session.Load(typeof(User), "joe_cool");
The User object you get back is live! Change its properties and it will get persisted to the database on next Flush()
.
// set Joe Cool's Last Login property joeCool.LastLogon = DateTime.Now; // flush the changes from the Session to the Database session.Flush();
All you had to do to get NHibernate to write the changes you made was to ask it to Flush
the Session. Go ahead and query your database through its tools to verify the Property LastLogin
was updated for "joe_cool".
Even better, you can query your table and get back a System.Collections.IList
of live objects. Try something like this:
IList userList = session.CreateCriteria(typeof(User)).List(); foreach(User user in userList) { System.Diagnostics.Debug.WriteLine(user.Id + " last logged in at " + user.LastLogon); }
This query will return the whole table. Typically you'll want a lot more control - like list the Users who have logged in after March 14, 2004 10:00 PM, so you'll do something like:
IList recentUsers = session.CreateCriteria(typeof(User)) .Add(Expression.Expression.Gt("LastLogon", new DateTime(2004, 03, 14, 20, 0, 0))) .List(); foreach(User user in recentUsers) { System.Diagnostics.Debug.WriteLine(user.Id + " last logged in at " + user.LastLogon); }
There are a wealth of querying options in the documentation, but this will give you an idea of the kind of power Hibernate offers you.
...And Finally Close()
your session. This will free up the ADO.NET connection that NHibernate is using.
// tell NHibernate to close this Session session.Close();
And that is it...
You've created objects, persisted them, and retrieved (via Criteria queries and key lookup). You've gotta be happy with that!
Now that you've got a feel for NHibernate, you'll be well served by having a good look through the extensive documentation that comes with Hibernate 2.0.3 (remember - the docs in NHibernate are still in the early stages). There's a world of cool stuff to explore like handling one-to-many SQL mapping, persisting sorted and nested collections, tuning performance and much more.
Enjoy! And Happy NHibernating!
Mike Doerfler
Once again - all credit should go to Glen Smith and his article that served as the template for this article