Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon

Data Access Layer

Save for later
  • 13 min read
  • 09 Nov 2016

article-image

In this article by Alexander Zaytsev, author of NHibernate 4.0 Cookbook, we will cover the following topics:

  • Transaction Auto-wrapping for the data access layer
  • Setting up an NHibernate repository
  • Using Named Queries in the data access layer

(For more resources related to this topic, see here.)

Introduction

There are two styles of data access layer common in today's applications. Repositories and Data Access Objects. In reality, the distinction between these two have become quite blurred, but in theory, it's something like this:

  • A repository should act like an in-memory collection. Entities are added to and removed from the collection, and its contents can be enumerated. Queries are typically handled by sending query specifications to the repository.
  • A DAO (Data Access Object) is simply an abstraction of an application's data access. Its purpose is to hide the implementation details of the database access, from the consuming code.

The first recipe shows the beginnings of a typical data access object. The remaining recipes show how to set up a repository-based data access layer with NHibernate's various APIs.

Transaction Auto-wrapping for the data access layer

In this recipe, we'll show you how we can set up the data access layer to wrap all data access in NHibernate transactions automatically.

How to do it...

  1. Create a new class library named Eg.Core.Data.
  2. Install NHibernate to Eg.Core.Data using NuGet Package Manager Console.
  3. Add the following two DOA classes:
    public class DataAccessObject<T, TId>
      where T : Entity<TId>
    {
    
      private readonly ISessionFactory _sessionFactory;
    
      private ISession session
      {
        get
        {
          return _sessionFactory.GetCurrentSession();
        }
      }
    
      public DataAccessObject(ISessionFactory sessionFactory)
      {
        _sessionFactory = sessionFactory;
      }	
    
      public T Get(TId id)
      {
        return WithinTransaction(() => session.Get<T>(id));
      }
    
      public T Load(TId id)
      {
        return WithinTransaction(() => session.Load<T>(id));
      }
    
      public void Save(T entity)
      {
        WithinTransaction(() => session.SaveOrUpdate(entity));
      }
    
      public void Delete(T entity)
      {
        WithinTransaction(() => session.Delete(entity));
      }
    
      private TResult WithinTransaction<TResult>(Func<TResult> func)
      {
        if (!session.Transaction.IsActive)
        {
          // Wrap in transaction
          TResult result;
          using (var tx = session.BeginTransaction())
          {
            result = func.Invoke();
            tx.Commit();
          }
          return result;
        }
    
        // Don't wrap;
        return func.Invoke();
      }
    
      private void WithinTransaction(Action action)
      {
        WithinTransaction<bool>(() =>
        {
          action.Invoke();
          return false;
        });
      }
    
    }
    
    public class DataAccessObject<T>
      : DataAccessObject<T, Guid>
      where T : Entity
    {
    }

How it works...

NHibernate requires that all data access occurs inside an NHibernate transaction.

Remember, the ambient transaction created by TransactionScope is not a substitute for an NHibernate transaction

This recipe, however, shows a more explicit approach. To ensure that at least all our data access layer calls are wrapped in transactions, we create a private WithinTransaction method that accepts a delegate, consisting of some data access methods, such as session.Save or session.Get. This WithinTransaction method first checks if the session has an active transaction. If it does, the delegate is invoked immediately. If it doesn't, a new NHibernate transaction is created, the delegate is invoked, and finally the transaction is committed. If the data access method throws an exception, the transaction will be rolled back automatically as the exception bubbles up to the using block.

There's more...

This transactional auto-wrapping can also be set up using SessionWrapper from the unofficial NHibernate AddIns project at https://bitbucket.org/fabiomaulo/unhaddins. This class wraps a standard NHibernate session. By default, it will throw an exception when the session is used without an NHibernate transaction. However, it can be configured to check for and create a transaction automatically, much in the same way I've shown you here.

See also

  • Setting up an NHibernate repository

Setting up an NHibernate Repository

Many developers prefer the repository pattern over data access objects. In this recipe, we'll show you how to set up the repository pattern with NHibernate.

How to do it...

  1. Create a new, empty class library project named Eg.Core.Data.
  2. Add a reference to Eg.Core project.
  3. Add the following IRepository interface:
    public interface IRepository<T>: IEnumerable<T>
      where T : Entity 
    { 
      void Add(T item);
      bool Contains(T item);
      int Count { get; }
      bool Remove(T item);
    }
  4. Create a new, empty class library project named Eg.Core.Data.Impl.
  5. Add references to the Eg.Core and Eg.Core.Data projects.
  6. Add a new abstract class named NHibernateBase using the following code:
    protected readonly ISessionFactory _sessionFactory;
    
    protected virtual ISession session
    {
      get
      {
        return _sessionFactory.GetCurrentSession();
      }
    }
    
    public NHibernateBase(ISessionFactory sessionFactory)
    {
      _sessionFactory = sessionFactory;
    }
    
    protected virtual TResult WithinTransaction<TResult>(
    Func<TResult> func)
    {
      if (!session.Transaction.IsActive)
      {
        // Wrap in transaction
        TResult result;
        using (var tx = session.BeginTransaction())
        {
          result = func.Invoke();
          tx.Commit();
        }
        return result;
      }
    
      // Don't wrap;
      return func.Invoke();
    }
    
    protected virtual void WithinTransaction(Action action)
    {
      WithinTransaction<bool>(() =>
      {
        action.Invoke();
        return false;
      });
    }
  7. Add a new class named NHibernateRepository using the following code:
    public class NHibernateRepository<T> : 
      NHibernateBase,
      IRepository<T> where T : Entity
    {
    
      public NHibernateRepository(
        ISessionFactory sessionFactory)
        : base(sessionFactory)
      {
      }
    
      public void Add(T item)
      {
        WithinTransaction(() => session.Save(item));
      }
    
      public bool Contains(T item)
      {
        if (item.Id == default(Guid))
          return false;
        return WithinTransaction(() => 
          session.Get<T>(item.Id)) != null;
      }
    
      public int Count
      {
        get
        {
          return WithinTransaction(() => 
            session.Query<T>().Count());
        }
      }
    
      public bool Remove(T item)
      {
        WithinTransaction(() => session.Delete(item));
        return true;
      }
    
      public IEnumerator<T> GetEnumerator()
      {
        return WithinTransaction(() => session.Query<T>()
               .Take(1000).GetEnumerator());
      }
    
      IEnumerator IEnumerable.GetEnumerator()
      {
        return WithinTransaction(() => GetEnumerator());
      }
    
    }

How it works...

The repository pattern, as explained in http://martinfowler.com/eaaCatalog/repository.html, has two key features:

  • It behaves as an in-memory collection
  • Query specifications are submitted to the repository for satisfaction.

In this recipe, we are concerned only with the first feature, behaving as an in-memory collection. The remaining recipes in this article will build on this base, and show various methods for satisfying the second point.

Because our repository should act like an in-memory collection, it makes sense that our IRepository<T> interface should resemble ICollection<T>.

Our NHibernateBase class provides both contextual session management and the automatic transaction wrapping explained in the previous recipe.

NHibernateRepository simply implements the members of IRepository<T>.

There's more...

The Repository pattern reduces data access to its absolute simplest form, but this simplification comes with a price. We lose much of the power of NHibernate behind an abstraction layer. Our application must either do without even basic session methods like Merge, Refresh, and Load, or allow them to leak through the abstraction.

See also

  • Transaction Auto-wrapping for the data access layer
  • Using Named Queries in the data access layer

Using Named Queries in the data access layer

Named Queries encapsulated in query objects is a powerful combination. In this recipe, we'll show you how to use Named Queries with your data access layer.

Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at ₹800/month. Cancel anytime

Getting ready

To complete this recipe you will need Common Service Locator from Microsoft Patterns & Practices. The documentation and source code could be found at http://commonservicelocator.codeplex.com.

Complete the previous recipe Setting up an NHibernate repository.

Include the Eg.Core.Data.Impl assembly as an additional mapping assembly in your test project's App.Config with the following xml:

<mapping assembly="Eg.Core.Data.Impl"/>

How to do it...

  1. In the Eg.Core.Data project, add a folder for the Queries namespace.
  2. Add the following IQuery interfaces:
    public interface IQuery
    {
    }
    
    public interface IQuery<TResult> : IQuery 
    {
      TResult Execute();
    }
  3. Add the following IQueryFactory interface:
    public interface IQueryFactory
    {
      TQuery CreateQuery<TQuery>() where TQuery :IQuery;
    }
  4. Change the IRepository interface to implement the IQueryFactory interface, as shown in the following code:
    public interface IRepository<T> 
      : IEnumerable<T>, IQueryFactory
      where T : Entity 
    {
      void Add(T item);
      bool Contains(T item);
      int Count { get; }
      bool Remove(T item);
    }
  5. In the Eg.Core.Data.Impl project, change the NHibernateRepository constructor and add the _queryFactory field, as shown in the following code:
    private readonly IQueryFactory _queryFactory;
    
    public NHibernateRepository(
      ISessionFactory sessionFactory,
      IQueryFactory queryFactory)
      : base(sessionFactory)
    {
      _queryFactory = queryFactory;
    }
  6. Add the following method to NHibernateRepository:
    public TQuery CreateQuery<TQuery>() where TQuery : IQuery
    {
      return _queryFactory.CreateQuery<TQuery>();
    }
  7. In the Eg.Core.Data.Impl project, add a folder for the Queries namespace.
  8. Install Common Service Locator using NuGet Package Manager Console, using the command.
    Install-Package CommonServiceLocator
  9. To the Queries namespace, add this QueryFactory class:
    public class QueryFactory : IQueryFactory 
    {
      private readonly IServiceLocator _serviceLocator;
    
      public QueryFactory(IServiceLocator serviceLocator)
      {
        _serviceLocator = serviceLocator;
      }
    
      public TQuery CreateQuery<TQuery>() where TQuery : IQuery
      {
        return _serviceLocator.GetInstance<TQuery>();
      }
    }
  10. Add the following NHibernateQueryBase class:
    public abstract class NHibernateQueryBase<TResult> 
      : NHibernateBase, IQuery<TResult>
    {
      protected NHibernateQueryBase(
        ISessionFactory sessionFactory)
        : base(sessionFactory) { }
    
      public abstract TResult Execute();
    }
  11. Add an empty INamedQuery interface, as shown in the following code:
    public interface INamedQuery
    {
      string QueryName { get; }
    }
  12. Add a NamedQueryBase class, as shown in the following code:
    public abstract class NamedQueryBase<TResult>
      : NHibernateQueryBase<TResult>, INamedQuery
    {
      protected NamedQueryBase(ISessionFactory sessionFactory)
        : base(sessionFactory) { }
    
      public override TResult Execute()
      {
        var nhQuery = GetNamedQuery();
        return Transact(() => Execute(nhQuery));
      }
    
      protected abstract TResult Execute(IQuery query);
    
      protected virtual IQuery GetNamedQuery()
      {
        var nhQuery = session.GetNamedQuery(QueryName);
        SetParameters(nhQuery);
        return nhQuery;
      }
    
      protected abstract void SetParameters(IQuery nhQuery);
    
      public virtual string QueryName
      {
        get { return GetType().Name; }
      }
    }
  13. In Eg.Core.Data.Impl.Test, add a test fixture named QueryTests inherited from NHibernateFixture.
  14. Add the following test and three helper methods:
    [Test]
    public void NamedQueryCheck()
    {
      var errors = new StringBuilder();
    
      var queryObjectTypes = GetNamedQueryObjectTypes();
      var mappedQueries = GetNamedQueryNames();
    
      foreach (var queryType in queryObjectTypes)
      {
        var query = GetQuery(queryType);
    
        if (!mappedQueries.Contains(query.QueryName))
        {
          errors.AppendFormat(
            "Query object {0} references non-existent " + 
            "named query {1}.",
            queryType, query.QueryName);
          errors.AppendLine();
        }
    
      }
    
      if (errors.Length != 0)
        Assert.Fail(errors.ToString());
    
    }
    
    private IEnumerable<Type> GetNamedQueryObjectTypes()
    {
      var namedQueryType = typeof(INamedQuery);
      var queryImplAssembly = typeof(BookWithISBN).Assembly;
    
      var types = from t in queryImplAssembly.GetTypes()
                  where namedQueryType.IsAssignableFrom(t)
                  && t.IsClass
                  && !t.IsAbstract
                  select t;
      return types;
    }
    
    private IEnumerable<string> GetNamedQueryNames()
    {
      var nhCfg = NHConfigurator.Configuration;
    
      var mappedQueries = nhCfg.NamedQueries.Keys
        .Union(nhCfg.NamedSQLQueries.Keys);
    
      return mappedQueries;
    }
    
    private INamedQuery GetQuery(Type queryType)
    {
      return (INamedQuery) Activator.CreateInstance(
        queryType, 
        new object[] { SessionFactory });
    }
  15. For our example query, in the Queries namespace of Eg.Core.Data, add the following interface:
    public interface IBookWithISBN : IQuery<Book>
    {
      string ISBN { get; set; }
    }
  16. Add the implementation to the Queries namespace of Eg.Core.Data.Impl using the following code:
    public class BookWithISBN : 
      NamedQueryBase<Book>, IBookWithISBN
    {
    
      public BookWithISBN(ISessionFactory sessionFactory)
        : base(sessionFactory) { }
    
      public string ISBN { get; set; }
    
      protected override void SetParameters(
        NHibernate.IQuery nhQuery)
      {
        nhQuery.SetParameter("isbn", ISBN);
      }
    
      protected override Book Execute(NHibernate.IQuery query)
      {
        return query.UniqueResult<Book>();
      }
    }
  17. Finally, add the embedded resource mapping, BookWithISBN.hbm.xml, to Eg.Core.Data.Impl with the following xml code:
    <?xml version="1.0" encoding="utf-8" ?>
    <hibernate-mapping >
      <query name="BookWithISBN">
        <![CDATA[
        from Book b where b.ISBN = :isbn
        ]]>
      </query>
    </hibernate-mapping>

How it works...

As we learned in the previous recipe, according to the repository pattern, the repository is responsible for fulfilling queries, based on the specifications submitted to it. These specifications are limiting. They only concern themselves with whether a particular item matches the given criteria. They don't care for other necessary technical details, such as eager loading of children, batching, query caching, and so on. We need something more powerful than simple where clauses. We lose too much to the abstraction.

The query object pattern defines a query object as a group of criteria that can self-organize in to a SQL query. The query object is not responsible for the execution of this SQL. This is handled elsewhere, by some generic query runner, perhaps inside the repository. While a query object can better express the different technical requirements, such as eager loading, batching, and query caching, a generic query runner can't easily implement those concerns for every possible query, especially across the half-dozen query APIs provided by NHibernate.

These details about the execution are specific to each query, and should be handled by the query object. This enhanced query object pattern, as Fabio Maulo has named it, not only self-organizes into SQL but also executes the query, returning the results. In this way, the technical concerns of a query's execution are defined and cared for with the query itself, rather than spreading into some highly complex, generic query runner.

According to the abstraction we've built, the repository represents the collection of entities that we are querying. Since the two are already logically linked, if we allow the repository to build the query objects, we can add some context to our code. For example, suppose we have an application service that runs product queries. When we inject dependencies, we could specify IQueryFactory directly. This doesn't give us much information beyond "This service runs queries." If, however, we inject IRepository<Product>, we have a much better idea about what data the service is using.

The IQuery interface is simply a marker interface for our query objects. Besides advertising the purpose of our query objects, it allows us to easily identify them with reflection.

The IQuery<TResult> interface is implemented by each query object. It specifies only the return type and a single method to execute the query.

The IQueryFactory interface defines a service to create query objects. For the purpose of explanation, the implementation of this service, QueryFactory, is a simple service locator. IQueryFactory is used internally by the repository to instantiate query objects.

The NamedQueryBase class handles most of the plumbing for query objects, based on named HQL and SQL queries. As a convention, the name of the query is the name of the query object type. That is, the underlying named query for BookWithISBN is also named BookWithISBN. Each individual query object must simply implement SetParameters and Execute(NHibernate.IQuery query), which usually consists of a simple call to query.List<SomeEntity>() or query.UniqueResult<SomeEntity>().

The INamedQuery interface both identifies the query objects based on Named Queries, and provides access to the query name. The NamedQueryCheck test uses this to verify that each INamedQuery query object has a matching named query.

Each query has an interface. This interface is used to request the query object from the repository. It also defines any parameters used in the query. In this example, IBookWithISBN has a single string parameter, ISBN. The implementation of this query object sets the :isbn parameter on the internal NHibernate query, executes it, and returns the matching Book object.

Finally, we also create a mapping containing the named query BookWithISBN, which is loaded into the configuration with the rest of our mappings.

The code used in the query object setup would look like the following code:

var query = bookRepository.CreateQuery<IBookWithISBN>();
query.ISBN = "12345";
var book = query.Execute();

See also

  • Transaction Auto-wrapping for the data access layer
  • Setting up an NHibernate repository

Summary

In this article we learned how to transact Auto-wrapping for the data access layer, setting up an NHibernate repository and how to use Named Queries in the data access layer

Resources for Article:


Further resources on this subject: