Repository implementation

Along with NHibernate mappings, Peniko.DataLayer project contains Repository implementations. Repository interfaces are in Peniko.Domain project, as mentioned before.

All communication with NHibernate is done through ISession instance. Session is basically a Unit of Work for NHibernate. We have a standard NHibernateHelper static class for encapsulating ISessionFactory and for creating / opening the session:

public static class NHibernateHelper

{

    private static ISessionFactory _sessionFactory;

 

    private static ISessionFactory SessionFactory

    {

        get

        {

            if (_sessionFactory == null)

            {

                var configuration = new Configuration();

                configuration.Configure();

                configuration.AddAssembly(typeof(Product).Assembly);

                _sessionFactory = configuration.BuildSessionFactory();

            }

            return _sessionFactory;

        }

    }

 

    public static ISession OpenSession()

    {

        return SessionFactory.OpenSession();

    }

}

SessionFactory is created from the NHibernate configuration the first time its accessed. Configuration comes from hibernate.cfg.xml file defined in UI and test projects:

<?xml version="1.0" encoding="utf-8" ?>

<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">

  <session-factory>

    <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>

    <property name="dialect">NHibernate.Dialect.MsSql2005Dialect</property>

    <property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>

    <property name="connection.connection_string">...</property>

    <property name="show_sql">true</property>

    <mapping assembly="Peniko.DataLayer" />

  </session-factory>

</hibernate-configuration>

In Peniko.DataLayer, every Repository method uses its own session, so we can't use NHibernate's lazy loading feature. Better option would be session-per-conversation pattern mentioned here, but we'll leave it as is for now.

The most important class in Repository implementation is Repository<T> implementing our IRepository<T> interface:

public interface IRepository<T> where T : Entity<T>

{

    void Save(T entity);

    void Delete(T entity);

    T GetById(Guid id);

    IList<T> GetAll();

}

 

public abstract class Repository<T> : IRepository<T> where T : Entity<T>

{

    private static readonly ILog _log = LogManager.GetLogger(typeof (Repository<T>));

    protected static ILog Log { get { return _log; } }

 

    public void Save(T entity) { ... }

    public void Delete(T entity) { ... }

    public T GetById(Guid id) { ... }

    public IList<T> GetAll() { ...  }

}

It's defined as abstract class and provides inherited classes with basic strongly typed methods for saving, deleting and retrieving all entities or a single entity by id and also an log4net's ILog instance . Inherited class is defined like this:

public class ProductRepository : Repository<Product>, IProductRepository { ... }

This is a Save method from Repository<T>:

public void Save(T entity)

{

    if (!entity.IsValid) throw new ValidationException("Entity is invalid");

 

    using (var session = NHibernateHelper.OpenSession())

    using (var transaction = session.BeginTransaction())

    {

        Log.DebugFormat("Saving entity Id: {0}", entity.Id);

 

        try

        {

            session.SaveOrUpdate(entity);

            AfterSaveEntity(session, entity);

            transaction.Commit();

        }

        catch (StaleObjectStateException ex)

        {

            transaction.Rollback();

            Log.Error("Error saving entity", ex);

            throw new ConcurrencyException("The data has been modified by another user", ex);

        }

        catch (HibernateException ex)

        {

            transaction.Rollback();

            Log.Error("Error saving entity", ex);

            throw;

        }

    }

}

First thing it does is to check if entity is valid. Then it opens a session and begins a new transaction on it. As of NHibernate 2.0, all Saves, Updates and Deletes must be encapsulated in transaction. Save method calls session.SaveOrUpdate() which does both insert and update operations based on the value of entity.Id. In our case, if Id is Guid.Empty, it will do Insert, otherwise Update.

AfterSaveEntity call allows inherited class to do some action after the entity is saved successfully. I.e. to save some other, unrelated entity.

Note that we catch StaleObjectStateException. That exception is thrown when there is a concurrency problem. We encapsulate it in our ConcurrencyException and throw that instead. That way it's easy to catch it in UI code without dependency on NHibernate.

Delete method is more or less the same. The only difference is that it calls session.Delete() instead of session.SaveOrUpdate().

GetById looks like this:

public T GetById(Guid id)

{

    using (var session = NHibernateHelper.OpenSession())

    {

        Log.DebugFormat("Getting entities with Id: {0}", id);

 

        try

        {

            var entity = session.Get<T>(id);

            AfterGetEntity(session, entity);

            return entity;

        }

        catch (HibernateException ex)

        {

            Log.Error("Error getting entities by id", ex);

            throw;

        }

    }

}

This method is very simple. session.Get<T>(id) returns a single entity with the given id. GetAll method is similar, but it uses NHibernate Criteria API and returns session.CreateCriteria(typeof (T)).List<T>() as a result.

In another project I'm currently involved, Repository also has FindAll and FindOne methods that accept DetachedCriteria as a parameter. The idea came from 12. episode of Summer of NHibernate.

Other Repository classes inherited from Repository<T> have its own methods for retrieving entities. These methods are mostly using Criteria API. More on Criteria API and HQL in NHibernate learning material.


0 comments:

Post a Comment