From Entity Framework to NHibernate

Recently I decided to change the data access layer in my blog from Entity Framework to NHibernate. In this post I'll tell you Why and How I did it.

The WHY

I still have some issues with the current version of the Entity Framework as I posted in the article MVC Storefront: Migrating to the Entity Framework. Many of the things I wanted to do with the EF like using POCOs or LazyLoading will be possible in version 4. Until then I decided to give NHibernate a shot. I figured it wouldn't be so hard since I already had some knowledge with Hibernate from my Java days.

The HOW

 The migration was not that hard. NHibernate has a great documentation with good examples. I also used the NHibernate in Action book which I highly recommend. It is only 400 pages and very to the point.

How the Project is Structured

I have a Website and 4 assemblies:

SpeakOut.Data: This is where the DAOs live, both their interface and implementation. Also in this assembly are the NHibernate mapping files.

SpeakOut.Model: This assembly has all the domain model entities. I had to make minor changes to these classes to get them to work best with NHibernate. The collections for instance were changed to use IList<T> instead of the IEnumerable<T> I was using with the EF.

SpeakOut.Service: This assembly has services (Interfaces and Implementations) that are used by the presentation layer.

SpeakOut.Lib: Here are all the utility classes, base classes for the pages and control and classes with extension methods.

The Model

This is the class diagram for the classes in the model. NHibernate makes populating the dependencies show in the diagram really easy, actually you don't do anything and you still get the benefit of lazy loading for free. This is something that wasn't easy in the EF.

The Mapping Files

Here are the mapping files for my entities.

Author

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
                   assembly="SpeakOut.Model"
                   namespace="SpeakOut.Model">
  <class name="Author">
    <id name="AuthorId">
      <generator class="guid"/>
    </id>
    <property name="Name"/>
    <property name="Login"/>
    <property name="Password"/>
    <property name="Email"/>
    <property name="IsAdmin"/>
    <bag name="Posts" access="field.camelcase-underscore" inverse="true" cascade="all-delete-orphan">
      <key column="AuthorId"/>
      <one-to-many class="Post"/>
    </bag>
  </class>
</hibernate-mapping>

Post

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
                   assembly="SpeakOut.Model"
                   namespace="SpeakOut.Model">
  <class name="Post">
    <id name="PostId">
      <generator class="guid"/>
    </id>
    <property name="Title"/>
    <property name="Body"/>
    <property name="Permalink"/>
    <property name="Published"/>
    <property name="PublishDate"/>
    <bag name="Comments" access="field.camelcase-underscore" inverse="true" cascade="all-delete-orphan">
      <key column="PostId"/>
      <one-to-many class="Comment"/>
    </bag>
    <bag name="Tags" access="field.camelcase-underscore" inverse="true" cascade="all-delete-orphan">
      <key column="PostId"/>
      <one-to-many class="Tag"/>
    </bag>
    <bag name="Categories" access="field.camelcase-underscore" table="CategoryPost">
      <key>
        <column name="PostId" not-null="true"/>
      </key>
      <many-to-many class="Category">
        <column name="CategoryId" not-null="true"/>
      </many-to-many>
    </bag>
    <many-to-one name="Author" column="AuthorId" not-null="true"/>
  </class>
</hibernate-mapping>

Comment

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
                   assembly="SpeakOut.Model"
                   namespace="SpeakOut.Model">
  <class name="SpeakOut.Model.Comment">
    <id name="CommentId">
      <generator class="guid"/>
    </id>
    <property name="Author"/>
    <property name="Body"/>
    <property name="Email"/>
    <property name="Website"/>
    <property name="Country"/>
    <property name="Ip"/>
    <property name="IsApproved"/>
    <property name="CreatedAt"/>
    <many-to-one name="Post" column="PostId" not-null="true"/>
  </class>
</hibernate-mapping>

Tag

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
                   assembly="SpeakOut.Model"
                   namespace="SpeakOut.Model">
  <class name="Tag">
    <id name="TagId">
      <generator class="guid"/>
    </id>
    <property name="Name"/>
    <many-to-one name="Post" column="PostId" not-null="true"/>
  </class>
</hibernate-mapping>

Category

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
                   assembly="SpeakOut.Model"
                   namespace="SpeakOut.Model">
  <class name="Category">
    <id name="CategoryId">
      <generator class="guid"/>
    </id>
    <property name="Name"/>
    <bag name="Posts" table="CategoryPost">
      <key>
        <column name="CategoryId" not-null="true"/>
      </key>
      <many-to-many class="Post">
        <column name="PostId" not-null="true"/>
      </many-to-many>
    </bag>
  </class>
</hibernate-mapping>

 The Data Access

 In the data access layer I decided to drop the Repository I was using to go with simple DAOs. The main reason I did it was to test NHibernate as recommended by it's more experienced users. I really think I wouldn't get a lot from the Repository.

Here is the class diagram for the DAOs interfaces:



One of the approaches suggested in the NHibernate in Action book is to use a base DAO that encapsulate the common behavior in all NHibernate DAOs. Here is the code for the this class:
 

namespace SpeakOut.Data.NHibernate.Daos
{
    public class BaseDao<T> : IBaseDao<T>
    {
        private ISession _session;

        protected ISession Session
        {
            get
            {
                if (_session == null)
                {
                    _session = NHibernateHelper.GetCurrentSession();
                }
                return _session;
            }
        }

        public T FindById(Guid id)
        {
            return Session.Load<T>(id);
        }

        public T FindByIdAndLock(Guid id)
        {
            return Session.Load<T>(id, LockMode.Upgrade);
        }

        public IList<T> FindAll()
        {
            return Session.CreateCriteria(typeof (T)).List<T>();
        }

        public T MakePersistent(T entity)
        {
            Session.SaveOrUpdate(entity);
            return entity;
        }

        public void MakeTransient(T entity)
        {
            Session.Delete(entity);
        }
    }
}

 All the DAOs derive from this BaseDao and get all the basic functionality. The specialized DAOs are left to implement the functionality that is directly related to them. Look at how simple the implementation of the AuthorDao is:

namespace SpeakOut.Data.NHibernate.Daos
{
    public class AuthorDao : BaseDao<Author>, IAuthorDao
    {
        public Author FindByLogin(string login)
        {
            return Session.CreateCriteria<Author>()
                .Add(Expression.Eq("Login", login).IgnoreCase())
                .UniqueResult<Author>();
        }
    }
}

 One aspect to pay attention to is how the NHibernate Session works when you have an ASP.NET application. The easiest way to deal with this is to create an HTTP Module that creates a session that will live as long as the duration of the Request. When the Request ends the Transaction is committed and the Session is released. A similar strategy is described in this post.

 Here is the implementation of the HTTP Module.

public class NHibernateCurrentSessionWebModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
        context.BeginRequest += Application_BeginRequest;
        context.EndRequest += Application_EndRequest;
    }

    public void Dispose()
    {
    }

    private void Application_BeginRequest(object sender, EventArgs e)
    {
        ISession session = NHibernateHelper.OpenSession();
        session.BeginTransaction();
        CurrentSessionContext.Bind(session);
    }

    private void Application_EndRequest(object sender, EventArgs e)
    {
        ISession session = CurrentSessionContext.Unbind(NHibernateHelper.SessionFactory);

        if (session == null) return;

        try
        {
            session.Transaction.Commit();
        }
        catch (Exception)
        {
            session.Transaction.Rollback();
        }
        finally
        {
            session.Close();
        }
    }
}

Since this module creates the Session and the Transaction we need a way to access these resources. This is done by a helper class like this:

namespace SpeakOut.Data.NHibernate
{
    public static class NHibernateHelper
    {
        public static readonly ISessionFactory SessionFactory;

        static NHibernateHelper()
        {
            try
            {
                Configuration configuration = new Configuration();
                configuration.AddAssembly("SpeakOut.Data");
                SessionFactory = configuration.Configure().BuildSessionFactory();
            }
            catch (Exception ex)
            {
                throw new Exception("NHibernate initialization failed", ex);
            }
        }

        public static ISession OpenSession()
        {
            return SessionFactory.OpenSession();
        }

        public static ISession GetCurrentSession()
        {
            return SessionFactory.GetCurrentSession();
        }
    }
}

There are two important things about this class. The first is AddAssembly method being called in the static constructor. I have to pass the name of the assembly that contains the NHibernate Mappings. In this project all the mappings are in the Speakout.Data assembly. The second important point is the GetCurrentSession which is the method that will be used in all the DAOs to get the Session already created in the HTTP Module.

More info about configuring NHibernate when using several assemblies can be found here.

The Configuration

Since I'm using a WebSite all the NHibernate configuration is done in the Web.Config. First a section is defined in the ConfigSections node:

<section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler,NHibernate"/>

And the create the configuration node:

<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
  <session-factory>
    <property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
    <property name="connection.connection_string">
      Server=(local);initial catalog=your_catalog;user id=your_user;password=your_pass
    </property>
    <property name="adonet.batch_size">10</property>
    <property name="show_sql">true</property>
    <property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property>
    <property name="use_outer_join">true</property>
    <property name="command_timeout">60</property>
    <property name="query.substitutions">true 1, false 0, yes 'Y', no 'N'</property>
    <property name="proxyfactory.factory_class">NHibernate.ByteCode.LinFu.ProxyFactoryFactory, NHibernate.ByteCode.LinFu</property>
    <property name="current_session_context_class">web</property >
  </session-factory>
</hibernate-configuration>

 Final Words

I hope my implementation may help you. It simple and small enough that you can go through it very quickly and get a base understanding of what you need to get NHibernate going.

I'll probably give the Entity Framework another chance once my hosting starts to support EF 4. After all changing and trying, and failing and learning is exactly what this blog is all about.

You can get all the source code for this project at Codeplex.

How to Match Date without Time in NHibernate

I'm changing my blog to use an NHibernate data layer. I'll post about this experience soon.

One thing I think is worth mentioning is how to write a query to filter by date only. Yes, I want to ignore the time part of the date class. I new to NHibernate and I thought that there would be a function to do this. Well there isn't. There is nothing bad about this I just wanted to write about it to let others find the recommended solution to solve this problem.

The way to do it is by using BETWEEN and comparing the hour zero (00:00:00) of the day and the last second of the day (23:59:59).

Here is an example where I need to find all posts published in a given date:

public IList<Post> FindByDate(DateTime datePublished)
{
    DateTime initDate = datePublished.Date;
    DateTime endDate = datePublished.Date.AddDays(1).AddSeconds(-1);
    return Session.CreateCriteria<Post>()
        .Add(Expression.Between("PublishDate", initDate, endDate))
        .List<Post>();
}

 And here is the same example using HQL:


public IList<Post> FindByDate(DateTime datePublished)
{
    DateTime initDate = datePublished.Date;
    DateTime endDate = datePublished.Date.AddDays(1).AddSeconds(-1);
    string hql = "from Post where PublishDate between :initDate and :endDate";
    return Session.CreateQuery(hql)
        .SetDateTime("initDate", initDate)
        .SetDateTime("endDate", endDate)
        .List<Post>();
}

Very simple. My only doubt was if there were any other way to do this. I found a discussion on the NHibernate group where Ayende says that this is the way to do it. Case closed :-)

 

Compressing Files and Folders with SharpZipLib

I have found a lot of examples on how to compress files using C# but only a few that would compress all files and folders (including subfolders). I found an example in this forum that show how to do it using SharpZipLib.

Here is what I did with it:

static void Main(string[] args)
{
    ZipOutputStream zip = new ZipOutputStream(File.Create(@"d:\my.zip"));
    zip.SetLevel(9);
    string folder = @"D:\Work\AnyDirectory\";
    ZipFolder(folder, folder, zip);
    zip.Finish();
    zip.Close();
}

public static void ZipFolder(string RootFolder, string CurrentFolder, 
    ZipOutputStream zStream)
{
    string[] SubFolders = Directory.GetDirectories(CurrentFolder);

    //calls the method recursively for each subfolder
    foreach (string Folder in SubFolders)
    {
        ZipFolder(RootFolder, Folder, zStream);
    }

    string relativePath = CurrentFolder.Substring(RootFolder.Length) + "/";

    //the path "/" is not added or a folder will be created
    //at the root of the file
    if (relativePath.Length > 1)
    {
        ZipEntry dirEntry;
        dirEntry = new ZipEntry(relativePath);
        dirEntry.DateTime = DateTime.Now;
    }

    //adds all the files in the folder to the zip
    foreach (string file in Directory.GetFiles(CurrentFolder))
    {
        AddFileToZip(zStream, relativePath, file);
    }
}

private static void AddFileToZip(ZipOutputStream zStream, string relativePath, string file)
{
    byte[] buffer = new byte[4096];

    //the relative path is added to the file in order to place the file within
    //this directory in the zip
    string fileRelativePath = (relativePath.Length > 1 ? relativePath : string.Empty) 
                              + Path.GetFileName(file);

    ZipEntry entry = new ZipEntry(fileRelativePath);
    entry.DateTime = DateTime.Now;
    zStream.PutNextEntry(entry);

    using (FileStream fs = File.OpenRead(file))
    {
        int sourceBytes;
        do
        {
            sourceBytes = fs.Read(buffer, 0, buffer.Length);
            zStream.Write(buffer, 0, sourceBytes);
        } while (sourceBytes > 0);
    }
}

This is not hard but I had to figure out that the file names needed to have the relative path before it in order to correctly place them in the same folders within the compressed zip file.

I hope this helps!

c#