Introduction
Hibernate has been huge in the Java world for being a easy to use high performance OR mapper, but only recently has it started to make a name for itself in the .NET world with NHibernate.
I've personally had a fair amount with different OR mappers, and object persistence frameworks. I decided to jump in and play with alpha .4 of NHibernate... and have honestly found it to be quite capable and relatively low on bugs (I haven't found any yet anyway).
Background
After reading a couple of articles here (NHibernate) on the subject of NHibernate, I had a general clue, but probably more questions then answers.
I decided about bundling some real world examples along with explanation of why I am doing what, and it should be a pretty good starting point for anyone wanting to dive into OR Mappers, whether it be hobby or an enterprise application.
I'll start out first by saying, initially my work was based of these examples, however... I've found that the examples were sometimes incorrect, or files weren't named, or procedures weren't explained. Anyhow I aim to help with that!
Article Goals
This is my first submission of any kind, so keep non-constructive criticism to a minimum. I am 100% open to feedback, I don't claim to have all the answers, or claim to have the best methods to achieve this or that.
Over the next couple in a series of articles, I'd like to demonstrate n-tier frameworks utilizing NHibernate.
In this part, it'll be a basic data layer logic that I will be referencing directly for the time being, but in later articles, I intend to implement my business logic layer, as well as security on my business methods.
Target audience... anyone who is interested in N-Tier development. As I said before, it'll help hobbyists, and people interested in getting started in enterprise development. As well as, maybe fill in a little bit where I struggled (because of lack of documentation). I'd also like to provide a starting point for people who are just getting into building robust applications (whether for business or hobby... no excuse for bad application design).
Getting Started...
These steps are required, however, the order in which you do them is debatable.
Database Schema - I am using the Northwind database that is so standard, or downloadable from Microsoft. For now, I only really need Customers and Orders, the others are not necessary.
Class Definitions - You need to write a class with at least one constructor that has 0 parameters, and public properties representing data in the database: Bags, or Collections (Customer.Orders
will end up being a IList
after everything is said and done, we'll go into this a little more later...).
Customer.cs
using System;
using System.Collections;
namespace nhibernator.BLL
{
public class Customer
{
#region Private Internal Members
private string m_CustomerID, m_CompanyName, m_ContactName,
m_Address, m_City, m_Region, m_PostalCode, m_Country;
private IDictionary m_Orders;
#endregion
#region Public Properties
public string CustomerID
{
get
{
return m_CustomerID;
}
set
{
m_CustomerID = value;
}
}
public string CompanyName
{
get
{
return m_CompanyName;
}
set
{
m_CompanyName = value;
}
}
public string ContactName
{
get
{
return m_ContactName;
}
set
{
m_ContactName = value;
}
}
public string Address
{
get
{
return m_Address;
}
set
{
m_Address = value;
}
}
public string City
{
get
{
return m_City;
}
set
{
m_City = value;
}
}
public string Region
{
get
{
return m_Region;
}
set
{
m_Region = value;
}
}
public string PostalCode
{
get
{
return m_PostalCode;
}
set
{
m_PostalCode = value;
}
}
public string Country
{
get
{
return m_Country;
}
set
{
m_Country = value;
}
}
public IDictionary Orders
{
get
{
return m_Orders;
}
set
{
m_Orders = value;
}
}
#endregion
public Customer()
{
}
}
}
Ok, so now we have Customer
class defined, we'll want to more or less duplicate this with Products, and orders.
Mapping file - <ClassName>.hbm.xml to be built as an embedded resource (defines how our Entities map to database objects).
You'll need one of these for each class you intend on persisting. Sounds time consuming, but comparing alternatives, it's relatively pain free.
Customer.hbm.xml
<HIBERNATE-MAPPING" xmlns="urn:nhibernate-mapping-2.0">
<CLASS name="nhibernator.BLL.Customer, nhibernator" table="Customers">
<ID name="CustomerID" column="CustomerID" type="String" length="20">
<GENERATOR class=assigned />
</ID>
-->
<property name="CompanyName"></property>
<property name="ContactName"></property>
<property name="Address"></property>
<property name="City"></property>
<property name="Region"></property>
<property name="PostalCode"></property>
-->
<SET name="Orders" cascade="all" lazy="true">
<KEY column="CustomerID" />
<ONE-TO-MANY class="nhibernator.BLL.Order, nhibernator" />
</SET>
</CLASS>
</HIBERNATE-MAPPING>
Unfortunately, Northwind has some questionable design practices. However, it's everywhere and thus I think it to be the perfect database for this example.
Normally, CustomerID
, the ID
field in Customer
would be int
or GUID
, and unique int
, and would have slightly different syntax. However, in this case it does not.
We can effectively "Alias" table field names to class properties like so:
<property name="CompName" column= "CompanyName" type="String" length="40"/>
However, I'd like to stick to database fields as much as possible. You can actually omit the column all together if you want the property to have the same name...
<property name="CompanyName" length="40"/>
You might also notice I've removed the type="string" bit of text. As of recent versions anyway NHibernate uses reflection to determine the type of the data (perhaps there are some performance ramifications, though really don't know).
Important note...
While you could probably load the mappings on the fly by specifying a filename, it seems to be a general practice to compile to embedded resource. Once you've created your .hbm.xml, make sure your build action is Embedded Resource like so:
So now, I leave you to write the mapping files for the other two persisted objects, or you can simply download the code!
NHibernate configuration - can be done through XML file, resource stream, or manually in code. (I chose the code route, as trial and error with other methods never worked as they should; don't know if this is error on my part, or side effect of being an alpha).
NHibernate needs to be told what database provider to use, what SQL dialect to use, and connection string. Normally, a config file would look like this:
="1.0" ="utf-8"
<HIBERNATE-CONFIGURATION xmlns="urn:nhibernate-configuration-2.0">
<SESSION-FACTORY name="nhibernator">
<property name="connection.provider">
NHibernate.Connection.DriverConnectionProvider
</property>
<property name="connection.driver_class">
NHibernate.Driver.SqlClientDriver</property>
<property name="connection.connection_string">
Server=localhost;initial catalog=Northwind;Integrated Security=SSPI
</property>
<property name="show_sql">false</property>
<property name="dialect">
NHibernate.Dialect.MsSql2000Dialect
</property>
<property name="use_outer_join">true</property>
<property name="query.substitutions">
true 1, false 0, yes 'Y', no 'N'
</property>
<MAPPING assembly="nhibernator" />
</SESSION-FACTORY>
</HIBERNATE-CONFIGURATION>
However, like I said, I couldn't get the embedded resource stream for that to work, so I've written up the following code to simulate it. (Not as easy to come along and change later, but sufficient for getting the idea across).
In my data layer class CustomerFactory.cs, I have this code in the constructor for the time being:
config = new Configuration();
IDictionary props = new Hashtable();
props["hibernate.connection.provider"] =
"NHibernate.Connection.DriverConnectionProvider";
props["hibernate.dialect" ] = "NHibernate.Dialect.MsSql2000Dialect";
props["hibernate.connection.driver_class"] =
"NHibernate.Driver.SqlClientDriver" ;
props["hibernate.connection.connection_string"] =
"Server=localhost;initial catalog=Northwind;Integrated Security=SSPI" ;
foreach( DictionaryEntry de in props )
{
config.SetProperty( de.Key.ToString(), de.Value.ToString() );
}
config.AddAssembly("nhibernator");
*Log4Net configuration - sure you could skip this, but then good luck making sense of errors when things don't work =) In my client app, I simply read a log4net.config on load, and start from there.
n-Tiered Frameworks...
If you've encountered any sort of professional or enterprise software development, you've undoubtedly had these concepts beaten into your brain, and probably for the good.
Too much abstraction is bad, but a decent separation of data, business logic, and presentation logic can improve efficiency, code readability, and general upkeep.
So I've created a root namespace, and then created DLL for data layer logic (sure I could have used something a little more informative, but I am a creature of habit).
A subnamespace (?) of BLL for business layer logic...
And finally a whole other project for my presentation logic (I like to know I can use my tiered logic in console, web, remoting, webservices etc.).
DataLayerLogic - By definition, this code really should probably handle getting/updating/inserting/deleting entities in the database as well as data validation (SQL injection proofing etc.)
Luckily, NHibernate handles a fair amount of what's supposed to be done in the data layer itself, and so we generally end up writing some minor data validation, and methods to fetch all, fetch a single entity, or fetch entities based on some criteria.
What I've done is create a separate "factory" for each entity type (CustomerFactory
, ProductFactory
, OrderFactory
). In the constructor, I set up the NHibernate session factory, and spawn a NHibernate isession
. Then each method to set/get data checks to see session.IsConnected
, and if it's not session.ReConnect()
. This is important as data isn't blocked the entire time my data factory is instantiated. I perform the logic, and then disconnect. On dispose of my factory class, I make sure session gets disposed etc.
I've got two methods built out for fetching customers in CustomerFactory.cs.
First... GetCustomers()
in which I simply get back an IList
of Customer
objects.
Secondly... GetCustomer(string CustomerID)
in which I feed it an ID such as "BERGS", and since I fetch with FETCHMODE.EAGER
flag, I get all the subcollections, without trying to access them.
BusinessLayerLogic - Theoretically, in a lot of cases, simply wrapping DataLayerLogic
methods/classes is sufficient. However I end to fire off events, and do extra validation that wouldn't be simple insertion problems.
In this article, I've not really built out this layer; look for it in the next in this series.
PresentationLogic - in this case, I simply create an instance of my DataLayerLogic
, or invoke some of the static methods, and update user interface appropriately. (One could use ASP.NET or WinForms, or upcoming XAML or any number of presentation methods here).
I was sort of in a rush in this case; however, my little WinForms app will fetch all customers, and insert hard-coded data as a new user. (I'll make this a lot more presentable in an update to this article.)
Of course, your namespace structure could look totally different (as most people have <companyname>.<technology>.<project>.<feature>), but for all intent and purposes, I've come up with this:
Notice the .hbm.xmls. You can actually throw these in a mappings folder or whatever makes things cleaner for you...
I can simply call a static method and get a collection of Customer
s which are data-bindable by default.
Where to go from here?
Nhibernate has a wealth of different configuration options for caching and performance tweaking, which I will get into in my second article. Unfortunately, there isn't a whole lot of documentation on the subject, so you can try looking at hibernate documentation.
Also in the next article, you can expect to see a mechanism for my n-tier BusinessLayerLogic
to check credentials for fetching the data.
In closing...
Like I said, I struggled with NHibernate. Getting it up and running the first time can be quite a pain; however, it can be done. The example works, but you'll probably want to download NHibernate separately from here and add nhibernate.dll to your "References".
Again, please leave constructive criticism only. This is my first article, and I realize it was on a broad subject. Thanks, and keep your eyes open for part 2!
History
v1 - First version of the article. No bugging me about layout, I'm working on it. Documentation too!