Introduction
This is my so long awaited demo project showcasing the use of Fluent NHibernate
and Linq to NHibernate (and some other interesting bits).
First of all, if you are completely new to NHibernate
, I encourage you to take a look at my previous introductory article here.
Disclaimer
Before commencing, I want to make a disclaimer about this project, as you may expect, this is just demo code, not intended to be taken too seriously, please use it just as a *soft* guide of how very basic things can be done using NHibernate
. However, I've tried to do my best implementing the code for this article, with time limitations and all the related issues that an active developer has.
Prerequisites
- .NET Framework 3.5 SP1
- Visual Studio 2008
- To run the test from inside Visual Studio: TestDriven.Net
- MSSQL Server 2005 with the AdventureWorks database installed
- All other dependencies (assemblies) are included within the solution
Let's Go
What is this about? Take a look at the solution structure:
I'm going to explain what’s the purpose of each of those projects.
NHibernateSample.Model
This is the simplest one. Here I've created the classes that are going to be mapped against our database, those are just POCO classes that represent a view of the real database. Take a look at this image:
Each class must override its GetHashCode
method (which should be implemented in a way that returns unique results for each unique entity) and its Equals
method in order to allow NHibernate to handle the loading and session caching process of entities. Those methods look similar in almost all the cases, except when we are handling entities that have composite ids.
Let's take a look at how this is being done in the AddressType
class that has its AddressTypeID
property as the primary key:
public override int GetHashCode()
{
return HashCodeGenerator.GenerateHashCode(AddressTypeID);
}
public override bool Equals(object obj)
{
return this.IsEqual(obj);
}
The GenerateHashCode
method from the HashCodeGenerator
class (NHibernateSample.Model/Helper/HashCodeGenerator.cs) and it looks like:
public static class HashCodeGenerator
{
public static int GenerateHashCode(params object[] keys)
{
int hash = 17;
foreach (var item in keys)
{
int itemHashCode;
if (item == null)
{
itemHashCode = 1;
}
else
{
itemHashCode = item.GetHashCode();
}
hash = hash * 23 + itemHashCode;
}
return hash;
}
}
As you can see, nothing glamorous, I'm just assuring that the result of the method is unique.
The override of the Equals
method is using the IsEqual
extension method from the EqualityHelper
class (NHibernateSample.Model/Helper/EqualityHelper.cs) and it looks like:
public static class EqualityHelper
{
public static bool IsEqual<T>(this T source, object obj) where T : class
{
var target = obj as T;
if (obj == null)
{
return false;
}
return target.GetHashCode().Equals(source.GetHashCode());
}
}
As you can see, it only checks the equality of the method GetHashCode
of the instances being passed to the method, simple enough.
NHibernateSample.ModelMapper
This is a very important project. Here is where I'm using the Fluent NHibernate
API to map our NHibernateSample.Model
assembly against our database.
One of the cool things that the Fluent
API provides to us is the use of conventions (following the “convention over configuration” spirit) that helps us to save a lot of time in the mapping process, for example, you may have a guideline for your database with regards to naming the primary key column of a given table, something like <Table_Name>ID, so, for the Product
table you have ProductID
table.
Using plain NHibernate
mapping files, you would have to go into the tedious work of mapping each of one entity, throwing away your convention.
Hopefully we are using Fluent NHibernate
and this is easy cake.
This project has few classes that inherit from FluentNHibernate.Mapping.ClassMap
which gives us all the facilities to configure our entities.
Let's take a look at our StateProvinceMapper
class:
public class StateProvinceMapper : ClassMap<StateProvince>
{
private const string schema = "Person";
public StateProvinceMapper()
{
SchemaIs(schema);
Id(x => x.StateProvinceID);
Map(x => x.Name)
.WithLengthOf(50)
.ReadOnly();
References(x => x.TerritoryID)
.LazyLoad()
.Not.Nullable();
}
}
As you can see, this is pretty straightforward.
- We set the schema of this entity, which is the “
Person
” schema in the AdventureWorks
database. - We set the
Id
of this entity, which is the StateProvinceID
property that maps directly to the same column in the database table. - We map our
Name
property against the Name
column in the database table, also, we set some attributes as its MaxLenght
and also we say that this is a readonly property. - We set a reference with another entity using our
TerritoryID
property (of StateProvince
type, which also maps to its corresponding table), also we set the LazyLoad
attribute that says to NHibernate
to do not retrieve it from the database until an explicit request is performed (stateProvinceInstance.TerritoryID.Name
would fire a database query) and lastly we mark this property as Not-Nullable so in Update
and Insert
operations, an attempt to store an StateProvince
entity with a null
value for its TerritoryID
property will throw an exception.
The place in charge of building the mapping from our Model against the database is the ModelBuilder
class (NHibernateSample.ModelMapper/ModelBuilder.cs), in there we are doing all the heavy work to create our mapping:
private static void buildModel()
{
IPersistenceConfigurer persistenceConfigurer = getPersistenceConfigurer();
cfg = persistenceConfigurer.ConfigureProperties(new Configuration());
var persistenceModel = new PersistenceModel();
persistenceModel.addMappingsFromAssembly(typeof(ModelBuilder).Assembly);
persistenceModel.Conventions.GetPrimaryKeyName = x => x.Name;
persistenceModel.Conventions.GetForeignKeyName = x => x.Name;
persistenceModel.Configure(cfg);
cfg.Configure();
}
Look how we are saying that the GetPrimaryKeyName
should be inferred directly from the property name and the same for the GetForeignName
method, so, if we have a class Product
, and we say that its ID
is ProductID
, its primary key should be mapped to the column ProductID
as well, and the same for its references.
You can look deeper into the project to get a more detailed view of what is going on there.
NHibernateSample.LINQModel
In this project, we are using the NHibernate.Linq
assembly to create a NHibernateContext
which has implemented a LINQ provider (not as good as LINQ to SQL) that allows us to create queries in a LINQ to SQL fashion.
The main class there is the ModelContext
class, which takes our entities and exposes them as IOrderedQueryable
implementations.
It is very simple, here is how it looks:
public class ModelContext : NHibernateContext
{
public ModelContext(ISession session)
: base(session)
{
}
public IOrderedQueryable<Customer> Customers
{
get
{
return Session.Linq<Customer>();
}
}
public IOrderedQueryable<Address> Addresses
{
get
{
return Session.Linq<Address>();
}
}
public IOrderedQueryable<AddressType> AddressTypes
{
get
{
return Session.Linq<AddressType>();
}
}
public IOrderedQueryable<CustomerAddress> CustomerAddresses
{
get
{
return Session.Linq<CustomerAddress>();
}
}
public IOrderedQueryable<SalesTerritory> SalesTerritories
{
get
{
return Session.Linq<SalesTerritory>();
}
}
public IOrderedQueryable<StateProvince> StateProvincies
{
get
{
return Session.Linq<StateProvince>();
}
}
}
As you can see, it takes an ISession
(which is our NHibernate
instance to handle the database management) and for each entity, I've created a wrapper that returns the IOrderedQueryable
that are going to allow us to work with them easily.
NHibernateSample.Tests
As its name says, there are just a bunch of tests ( I'm using XUnit as my unit test suite) that I've built to show how our ModelContext
class can be used to perform simple operations as projections or to create aggregates. I reckon that the tests in there sucks. I'm in the process of learning how to write proper tests, so this is not a good example of how a set of unit tests should be written.
Let's move on and take a look at the AddressTests
class, there I have the following test:
[Fact]
public override void Test_Retrieve_Entity_With_EntityID_Equals_To_X_Should_Return_Null()
{
int addressID = 0;
using (var context = new ModelContext(Session))
{
var result = (from address in context.Addresses
where address.AddressID == addressID
select address).SingleOrDefault();
Assert.Null(result);
}
}
As you can see, it’s very similar to what you can do using LINQ to SQL, but with NHibernate
:)
This is another test from the same class:
[Fact]
public override void Test_Sorting_Descending()
{
using (var context = new ModelContext(Session))
{
var result = (from address in context.Addresses
where address.City == "Bothell"
orderby address.AddressID descending
select address).ToList();
Assert.False(result.Last().AddressID > result.First().AddressID);
foreach (var item in result)
{
Console.WriteLine(item.AddressID);
}
}
}
Very self-explanatory. You can check the other test to have some fun with the power (and several limitations) of the LINQ provider for NHibernate
.
NHibernateSample.BusinessLayer
This project is intended to be used for our web applications. I think that the interesting bits are related to the session management (NHibernateSample.BusinessLayer/SessionManagement/SessionManager.cs) and the use of StructureMap
to configure our dependencies (NHibernateSample.BusinessLayer/Bootstrapper.cs, NHibernateSample.BusinessLayer/Repositories/ICustomerRepository.cs and NHibernateSample.BusinessLayer/Repositories/Implementations/CustomerRepository.cs).
NHibernateSample.CustomerWebSite
This project shows how you can use NHibernate
to perform a basic CRUD operation over our Customer
entity. I'll try to find the time to create a more complex example (I can't assure this :D), but with that simple aspx page you should be able to go on your own and make a nicer web app.
Some interesting things are happening in the global.asax file, and also it will help in the process to integrate ASP.NET controls with NHibernate
(GridView
, ObjectDataSource
).
Finally, please don't forget to update your database connection in order to run the samples. The file that you need to update is located in the Solution Items folder in the solution tree, and is named hibernate.cfg.xml, you should change the value for the property connection.connection_string
with one that matches your environment.
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="current_session_context_class">
managed_web
</property>
<property name="connection.connection_string">
Data Source=YOUR_DATABASE_SERVER_GOES_HERE;
Initial Catalog=AdventureWorks;Integrated Security=True
</property>
</session-factory>
</hibernate-configuration>
Please, if you find a WTF in the source code, don't hesitate to send your feedback, I'll be very thankful.
You can download the full demo here.
Bye bye.
Shameless plug: You can check this article on my blog here.