Title: An All-inclusive Data Access Layer for NHibernate
Author: Antoine Jaussoin
Email: a.jaussoin@laposte.net
Member ID: 1461364
Language: C#
Platform: Windows
Technology: NHibernate, C#, ASP.NET, .NET
Level: Intermediate
Description: A Domain Driven architecture using NHibernate for an easy to use Data Access Layer
Section Type the Code Project Section you Wish the Article to Appear
SubSection Type the Code Project SubSection you Wish the Article to Appear
License: CPOL
Introduction
Writing a clean Data Access Layer (DAL) is often a tricky thing to do.
Most of the time you end up with a DAL that is bleeding into your UI, and you don't (or can't) keep the separation of concern between your UI, the Business Layer (the logic of your application), and your DAL.
This article (and this project) is aimed to help you achieve a pure separation between these layers, using techniques and frameworks such as:
- NHibernate
- Inversion of Control (IoC, Windsor Castle)
- Unit testing using NUnit
It is also very important to mention that this framework is completely independent from NHibernate. The only implementation of the DAO/Repository layer is using NHibernate at the moment, but nothing prevents you from implementing this using something else.
Where to download
Your best shot is the CodePlex website.
You can either download the latest release, or even the trunk.
The address is: CodePlex
Background
This project (FT.Architecture) started as a project aimed at creating NHibernate-powered ASP.NET controls.
When developing this, it deviated into the creation of a full framework, and now the NHibernate-powered controls are just a small part of it.
Code Snippets: what the framework will provide
Everything in this framework works around a concept: the Unit of Work.
A Unit of Work (as explained by Martin Fowler here: http://martinfowler.com/eaaCatalog/unitOfWork.html)
Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems
In other word, everything that happens inside a UoW is transactional (very much like a database transaction). Actually, the UoW described here is a wrapper around NHibernate's ISession, which is a wrapper around a database transaction.
Let's see that in pratice.
Important: this is only a tiny slice of what the framework provide, you'll need to download it and try it to see all the other methods.
Basic stuff: Get by ID
[csharp]
using (var t = DAL.GetUnitOfWork())
{
IHuman human = t.GetRepository().GetById(123);
System.Console.Out.WriteLine(human.Name);
t.Commit();
}
[/csharp]
As you can see above, no SQL code or NHibernate code has been written! Actually, the client code that uses this doesn't have any reference to either NHibernate, ADO.NET, or anything like that.
The generic Repository we get contains many more methods such as GetAll, GetByMember, GetCount, GetRandom, etc.
Most of the time you won't have to code a single query to get objects from the database, ready to use.
Get By Member (recursive!)
[csharp]
using (var t = DAL.GetUnitOfWork())
{
IList humans = t.GetRepository().GetAllByMember(x => x.Father.Father.Name, "Anthony");
t.Commit();
}
[/csharp]
This will, effortlessly, return all the IHuman objects whose grand-father was named "Anthony"! And that, for free!
You'll also notice that the "Father.Father.Name" property was not passed as a (fragile) string, but rather by a lambda expression, which supports compile-time checking and easy refactoring!
Linq Provider
[csharp]
using (var t = DAL.GetUnitOfWork())
{
var humans = from IHuman h
in t.GetRepository().GetLinq()
where h.Name.StartsWith("Ant")
select h;
t.Commit();
}
[/csharp]
As you can see, using Linq is very easy! Just call the GetLinq() method on the repository of your choice, and it will return
an IQueryable collection you can interfere with. This will generate (thanks to Linq of NHibernate) the needed SQL.
ASP.NET: Base Page and Base Control facilities
Unit of Work for free
Provided you are using the NHibernateUnitOfWorkHttpModule as a HttpModule in your web application, and provided you make your page and control inherit from the relevant BasePage and BaseControl, you'll have an open Unit of Work directly from your page!
You can then write code like this within your code behind:
[csharp]
protected void Page_Load(object sender, EventArgs e)
{
//That gives you a random IHuman entity for free
IHuman human = UnitOfWork.GetRepository().GetRandom();
}
[/csharp]
As you can see, no need to open a transaction, or a database connection, or anything... it just works out of the box, and your entire DAL is there for free.
Storing entities across postbacks
As you probably know, the main problem with HTTP is that this protocol is stateless. It means you have to invent mechanism (such as ViewState, Session, etc.) to store data across each postback.
Storing an entity is not easy, as you don't want to store the entire object-tree on the viewstate! This would make the page way too big...
It's where GetEntity and SetEntity comes to the rescue: you can store an entity transparently across postbacks, and the only things that will actually be stored into the ViewState is the entity ID! (along with its type).
That will have a very small memory footprint, and it will be very efficient.
This is how it works:
[csharp]
//Stores an entity
public IHuman SelectedHuman
{
get { return GetEntity("human"); }
set { SetEntity("human", value); }
}
//Stores a simple field (serialised)
public string StoredString
{
get { return GetField("myString","default-value"); }
set { SetField(value, "myString"); }
}
[/csharp]
There's also a facility to store collections of entities (SetEntities and GetEntities).
The Architecture
Separation of concerns
As you can see in the schema above, we achieve here a full separation of concerns:
- The Domain doesn't reference anything: it is completely isolated, therefore it can be fully tested without relying on anything else
- The Data Access only references the Domain
- The UI (Web Client here, but it could be anything) only knows about the Domain. The client doesn't know anything about the Data Access layer! Only the IoC makes the link.
The different libraries
Here is a detailed explanation of what each library (or package does):
- FT.Architecture: Contains all the interfaces for the base Repositories. This is where methods like GetAll, GetByMember etc. are defined (but not implemented).
- FT.Architecture.NHibernate: Contains the implementation of the interfaces mentionned before, using NHibernate.
- YourProject.Core: It's where your entities (POCOs objects) should sit, along with their NHibernate mapping if needed.
It's also where you will extend the base Repositories interfaces of FT.Architecture, by adding more specialised methods that are needed for your model (such as GetHumansUnder18 for example). Exactly like FT.Architecture, this will only be interfaces. This library doesn't know anything about NHibernate, SQL or anything like that.
- YourProject.Data.NHibernate: Contains the implementation of YourProject.Core. This will typically contains the implementation of GetHumansUnder18 that we defined as an interface in YourProject.Core.
- The UIs: They will directly reference YourProject.Core and FT.Architecture, and will in practice use YourProject.Data.NHibernate by using the Inversion of Control mechanism.
All right: I want to play with it!
The archive provided will allow you to play with it out of the box, as long as you complete these few steps:
- Install SQL Server (Full or Express), 2005 or 2008, and the Management Studio
- Open Management Studio, and create a new database called "ncontrols"
- Download (http://ncontrols.codeplex.com/) and unzip the archive
- Open the solution, compile and run the WinForm project
- Go on the "Database Setup" tab
- Click on "Create Database", which should create the tables, and fill the database with dummy data, so you can play with it
- (bonus: if you run the unit tests, make sure you also created another database called "ncontrolstwo")
Pretty simple uh?
(note: if you have SQL Server (not Express), you might need to change the connection string on the nhibernate.config file on the project you are trying to run).
Copyrights
Part of my code uses someone elses code, which are:
- Some classes are using Billy McCafferty code, from his S#arp Architecture project, with some modifications
- HttpSimulator was coded by Phil Haack
If I forgot to mention someone, please advise and I'll be happy to correct that!
History
- v1.0: first version, likely to be corrected/amended