NHibernate 3.0: Using Named Queries in the Data Access Layer

NHibernate 3.0 Cookbook


October 2010

$26.99

Get solutions to common NHibernate problems to develop high-quality performance-critical data access applications

 

NHibernate 3.0 Cookbook

NHibernate 3.0 Cookbook

Get solutions to common NHibernate problems to develop high-quality performance-critical data access applications

  • Master the full range of NHibernate features
  • Reduce hours of application development time and get better application architecture and performance
  • Create, maintain, and update your database structure automatically with the help of NHibernate
  • Written and tested for NHibernate 3.0 with input from the development team distilled in to easily accessible concepts and examples
  • Part of Packt's Cookbook series: each recipe is a carefully organized sequence of instructions to complete the task as efficiently as possible
        Read more about this book      

(For more resources on NHibernate, see here.)

Getting ready

Download the latest release of the Common Service Locator from http://commonservicelocator.codeplex.com, and extract Microsoft.Practices.ServiceLocation.dll to your solution's libs folder.

Complete the previous recipe, Setting up an NHibernate repository.

Following the Fast testing with SQLite in-memory database recipe in the previous article, create a new NHibernate test project named Eg.Core.Data.Impl.Test.

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:

    {
    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. To the Eg.Core.Data.Impl project, add a reference to Microsoft.Practices.ServiceLocation.dll.
  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(
    ((INamedQuery) this).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 xmlns="urn:nhibernate-mapping-2.2">
    <query name="BookWithISBN">
    <![CDATA[
    from Book b where b.ISBN = :isbn
    ]]>
    </query>
    </hibernate-mapping>

        Read more about this book      

(For more resources on NHibernate, see here.)

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.

There's more...

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
  • Using ICriteria in the data access layer
  • Using Paged Queries in the data access layer
  • Using LINQ specifications in the data access layer

Summary

In this article we covered:

  • Using Named Queries in the data access layer

In the next article will cover using ICriteria and Paged Queries.


Further resources on this subject:


Books to Consider

comments powered by Disqus
X

An Introduction to 3D Printing

Explore the future of manufacturing and design  - read our guide to 3d printing for free