Click here to Skip to main content
11,631,864 members (72,806 online)
Click here to Skip to main content

Tagged as

Fluent NHibernate and Linq2NHibernate – Demo Project

, 2 Jul 2009 CPOL 49.1K 37
Rate this:
Please Sign up or sign in to vote.
This is my so long awaited demo project showcasing the use of Fluent NHibernate and Linq to NHibernate (and some other interesting bits).

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:

Solution-1

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:

ModelDiagram

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.

  1. We set the schema of this entity, which is the “Person” schema in the AdventureWorks database.
  2. We set the Id of this entity, which is the StateProvinceID property that maps directly to the same column in the database table.
  3. 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.
  4. 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()
{
    // initialize persistence configurer
    IPersistenceConfigurer persistenceConfigurer = getPersistenceConfigurer();
    // initialize nhibernate with persistence configurer properties
    cfg = persistenceConfigurer.ConfigureProperties(new Configuration());
    // add mappings definition to nhibernate 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);

    //Mix mode, this line allows us to append config settings to our configuration
    //instance from .config files (web.config,app.config)
    // or any other "traditional" (xml based) approach.
    //NOTE: If you are going to go with full Fluent configuration, 
    //you should remove the line below
    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 Smile | :)
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 Big Grin | :-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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

emiaj
Web Developer
Peru Peru
No Biography provided

You may also be interested in...

Comments and Discussions

 
GeneralFew things Pin
Mike Marynowski5-Jan-10 11:03
memberMike Marynowski5-Jan-10 11:03 
GeneralRe: Few things Pin
emiaj6-Jan-10 6:45
memberemiaj6-Jan-10 6:45 
GeneralRe: Few things Pin
Mike Marynowski6-Jan-10 7:31
memberMike Marynowski6-Jan-10 7:31 
GeneralRe: Few things Pin
emiaj6-Jan-10 9:04
memberemiaj6-Jan-10 9:04 
GeneralRe: Few things Pin
emiaj6-Jan-10 9:08
memberemiaj6-Jan-10 9:08 
GeneralRe: Few things Pin
Mike Marynowski6-Jan-10 12:22
memberMike Marynowski6-Jan-10 12:22 
GeneralBreaks.. Pin
bnturbanator2-Oct-09 21:02
memberbnturbanator2-Oct-09 21:02 
Generalversion 2005 Pin
bnturbanator1-Oct-09 1:24
memberbnturbanator1-Oct-09 1:24 
GeneralRe: version 2005 Pin
emiaj1-Oct-09 5:23
memberemiaj1-Oct-09 5:23 
QuestionDemo Project update please [modified] Pin
EM1L16-Sep-09 0:33
memberEM1L16-Sep-09 0:33 
AnswerRe: Demo Project update please [modified] Pin
EM1L16-Sep-09 1:46
memberEM1L16-Sep-09 1:46 
GeneralRe: Demo Project update please Pin
emiaj16-Sep-09 4:52
memberemiaj16-Sep-09 4:52 
GeneralGood article Pin
Virat Kothari5-Sep-09 12:01
memberVirat Kothari5-Sep-09 12:01 
GeneralRe: Good article Pin
emiaj7-Sep-09 4:41
memberemiaj7-Sep-09 4:41 
Generalwell done Pin
dinolee6-May-09 7:07
memberdinolee6-May-09 7:07 
GeneralRe: well done Pin
emiaj6-May-09 10:34
memberemiaj6-May-09 10:34 
GeneralMore please Pin
jalchr22-Apr-09 2:08
memberjalchr22-Apr-09 2:08 
GeneralRe: More please Pin
emiaj22-Apr-09 3:29
memberemiaj22-Apr-09 3:29 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150728.1 | Last Updated 2 Jul 2009
Article Copyright 2009 by emiaj
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid