NHibernate is a great ORM. It's mature. It's feature rich. It's free. Source code is available if necessary.
It now also supports Linq and Linq is great!
This is a quick guide to getting up and running with NHibernate and Linq quickly. The overall experience feels vaguely similar to LinqToSql, without all the drawbacks and lock-in of LinqToSql.
We are going to assume our database already exists. We are going to assume that database is Northwind, and we are going to assume that we are doing database driven design (as opposed to domain driven design). Northwind setup is described below.
We are going to use Visual Studio 2008 with Service Pack 1 and SQL Server 2008 Express. (Note: I'm not sure if everything here will work with Visual Web Developer 2008 Express Edition (a completely free stack). I have Visual Web Developer 2008 Express Edition installed, but I'm not sure if the Active Writer install will work with it since the file type isn't showing up for me.)
To speed the process along, we are going to use Castle Active Record to implement a basic Active Record pattern.
This means we won't be using NHibernate's famous XML mapping files.
And to really speed things along, we are going to use Castle ActiveWriter to get some basic Active Record code generation.
While I wouldn't necessarily use this stack for a production application, it's a very quick way to get up and running. This example is built on NHibernate and so all that goodness is still available later when necessary. You aren't really tied to any proprietary or inflexible features.
Step 1:
Download and install ActiveWriter on top of VS 2008. The download link is available from here:
http://using.castleproject.org/display/Contrib/ActiveWriter
At the time of writing, the current version is "Preview 4.1 (26/06/2008)".
I rebooted after the install since I have been burned one too many times, but rebooting is possibly not necessary.
Step 2:
Next, we will download and install the Northwind database.
Jeff Atwood provides approximate instructions here (ask specific questions in the comments if you get stuck):
http://www.codinghorror.com/blog/archives/000434.html
Follow the path for installing the binary files from the command line. I have tested that the SQL 2005 instructions work on SQL 2008.
Step 3:
Download Castle Active Record:
http://www.castleproject.org/castle/download.html
At the time of writing, the current version is "ActiveRecord 2.0 - August 1st, 2009". Unzip it and remember where you put it, you'll need that info in the next step.
Step 4:
Start Visual Studio 2008
We will create a new console application project:
File -> New -> Project
Visual C# -> Windows -> Console Application
ConsoleApplication1 -> OK
We need to add the appropriate NHibernate & Castle Active Record references:
Solution Explorer
Right Click ConsoleApplication1 -> References -> Add
Browse Tab
Go to your Castle ActiveRecord 2.0 download location and add:
Castle.ActiveRecord.dll
Castle.DynamicProxy2.dll
NHibernate.dll
NHibernate.Linq.dll
Click OK
And add a reference to System.Configuration as well:
Solution Explorer
Right Click ConsoleApplication1 -> References -> Add
.NET Tab
System.Configuration
Click OK
Step 5:
We will add the ActiveWriter1.actiw files to the project:
Solution Explorer
Right Click ConsoleApplication1 -> Add -> New Item...
Visual C# Items -> ActiveWriter (it's at the bottom of the list for me)
Add
Click OK on the Security Warning
Close the class details window
Step 6:
We will find the Northwind database in the Server Explorer:
View -> Server Explorer
Data Connections -> Add Connection...
Choose your Server name
Choose the correct login info
Select "Northwind" as the database
Click "Test Connection"
Assuming that succeeds, click OK
Expand the Northwind Tables node
Now drag and drop the following tables to the Active Writer Designer surface:
Orders
Shippers
Save the ActiveWriter1.actiw file
Click OK on the Security Warning
Close the ActiveWriter1.actiw file
You can look through the new ActiveWriter1.cs file in Solution Explorer to see the generated classes we are going to use later.
Step 7:
We will now add an Application Configuration File to the project and put the Northwind Connection String into it:
Solution Explorer
Right Click ConsoleApplication1 -> Add -> New Item...
Visual C# Items -> General -> Application Configuration File
Add
Modify the file to look like this and use your specific DB connection info:
--
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<add name="Northwind" connectionString="Data Source=ServerName;Initial Catalog=Northwind;User ID=sa;Password=password;"/>
</connectionStrings>
</configuration>
--
Step 8:
Open the main "Program.cs" class and add the following new methods:
private static void InitializeNHibernateActiveRecord()
{
string connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["Northwind"].ToString();
InPlaceConfigurationSource configuration = InPlaceConfigurationSource.Build(DatabaseType.MSSQLServer2005, connectionString);
ActiveRecordStarter.Initialize(configuration,
typeof(Orders),
typeof(Shippers)
);
}
private static ISession CreateNHibernateActiveRecordSession()
{
// This uses the values setup during ActiveRecordStarter.Initialize() to configure and create the session
return ActiveRecordMediator.GetSessionFactoryHolder().CreateSession(typeof(ActiveRecordBase));
}
Add the following code to the Main Method:
static void Main(string[] args)
{
try
{
InitializeNHibernateActiveRecord();
using (ISession dbSession = CreateNHibernateActiveRecordSession())
{
var queryToExecute = from oneShippers in dbSession.Linq<Shippers>()
select oneShippers;
foreach (Shippers oneShipper in queryToExecute.ToList())
{
Console.WriteLine("oneShipper ShipperID=[" + oneShipper.ShipperID + "] CompanyName=[" + oneShipper.CompanyName + "]");
} }
}
catch (Exception oneException)
{
Console.WriteLine("oneException=[" + oneException + "]");
throw; // you can remove this if you'd rather the program exit "more normally"
}
}
Add the following using statements at the top of Program.cs:
using Castle.ActiveRecord;
using Castle.ActiveRecord.Framework.Config;
using NHibernate;
using NHibernate.Linq;
Step 9:
Copy the NHibernate.ByteCode.Castle.dll file from your Castle Active Record download unzip directory to your projects bin\Debug\ folder.
Step 10:
Run the application:
Ctrl-F5 (Debug -> Start Without Debugging)
And you should see the following output:
--
oneShipper ShipperID=[1] CompanyName=[Speedy Express]
oneShipper ShipperID=[2] CompanyName=[United Package]
oneShipper ShipperID=[3] CompanyName=[Federal Shipping]
Press any key to continue . . .
--
So, the basics are working.
I would have loved to show some Order Details instead of Shippers, but there appear to be a lot of complexities with the Northwind database that don't seem to work well with the current release versions of ActiveWriter / ActiveRecord. Hopefully those issues will be fixed over time.
Step 11:
Now let's do a query which requires a join.
The CustomerID field on the Orders class first needs to have it's column type changed for it to work.
(Re)open the ActiveWriter1.actiw file.
Right Click on Orders -> CustomerID -> Properties
Set the Column Type to String
Step 12:
Let's model the relationship between Orders and Shippers in the Designer.
The foreign key relationship is Orders.ShipVia <-> Shippers.ShipperID, with one shipper having 0 to many orders.
With the ActiveWriter1.actiw designer open, go to the toolbox (View -> Toolbox).
Select the "Many To One Relationship" option under "ActiveWriter".
Click (and hold the click) on the Orders table and drag the line onto the Shippers table.
Highlight the line between the two tables and right click -> Properties.
Set the Source Column to ShipVia
Set the Target Table to Orders
Step 13:
Let's add a Linq query to the Main method for Orders to exercise that new relationship:
var queryToExecute = from oneOrders in dbSession.Linq<Orders>()
select oneOrders;
foreach (Orders oneOrder in queryToExecute.ToList())
{
Console.WriteLine("oneOrder OrderID=[" + oneOrder.OrderID + "] ShipVia=[" + oneOrder.ShipVia + "] Shippers.CompanyName=[" + oneOrder.Shippers.CompanyName + "]");
}
Step 14:
Run the application:
Ctrl-F5 (Debug -> Start Without Debugging)
And you should see the following output:
--
oneOrder OrderID=[10248] ShipVia=[3] Shippers.CompanyName=[Federal Shipping]
oneOrder OrderID=[10249] ShipVia=[1] Shippers.CompanyName=[Speedy Express]
<snip>
oneOrder OrderID=[11076] ShipVia=[2] Shippers.CompanyName=[United Package]
oneOrder OrderID=[11077] ShipVia=[2] Shippers.CompanyName=[United Package]
--
We have successfully executed a join query through Linq.
I was somewhat surprised when I found out this was running 4 queries under the covers. Basically:
SELECT Orders.*, Shippers.* FROM Orders LEFT OUTER JOIN Shippers ON Orders.ShipVia = Shippers.ShipperID
SELECT * FROM Orders WHERE ShipVia = 2
SELECT * FROM Orders WHERE ShipVia = 1
SELECT * FROM Orders WHERE ShipVia = 3
I either expected 1 query (eager fetch) or 831 queries (lazy fetch - 1 query for the list of orders + 1 query for each order to get the Shipper Company Name).
The results of first query contain enough information to display what the code is asking for, so it's definitely eager fetch behavior.
Changing the code to only print OrderID does not appear to change which queries are run.
So don't necessarily make assumptions about how your ORM is going to be generating queries under the covers, especially if performance seems to be an issue when it shouldn't be.
Some additional things to note:
Castle ActiveRecord provides an ActiveRecordLinq.AsQueryable method that would appear to be useable in place of the ISession.Linq method (with a call to new up a SessionScope instead of calling CreateSession), but I couldn't get that to work (I didn't dig into it).
While the code sample above makes for a decent demo and a quick start, your mileage will likely vary. When things work great, they work great. This sample didn't end up being as I planned though because I had to work around / avoid things that I couldn't easily get to work (like Order Details). In the end, I am very satisfied that the code sample is pretty short and straight-forward, yet so much power is happening behind the scenes.
NHibernate supports pretty much all the weirdness your database needs, but ActiveRecord supports less weirdness, and ActiveWriter supports even less. The higher up the stack/abstraction you get, the more likely edge cases haven't been tested/implemented yet.
Now that you have the basic NHibernate Linq infrastructure in place, there are plenty of Linq examples and sample code available elsewhere.
Enjoy!