NHibernate 3.0: Using Named Queries in the Data Access Layer

Exclusive offer: get 50% off this eBook here
NHibernate 3.0 Cookbook

NHibernate 3.0 Cookbook — Save 50%

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

$26.99    $13.50
by Jason Dentler | October 2010 | .NET Cookbooks Open Source Web Development

Named Queries encapsulated in query objects is a powerful combination. This article by Jason Dentler, author of NHibernate 3.0 Cookbook, shows you how to use Named Queries with your NHibernate data access layer.

The reader will benefit from the previous article, NHibernate 3.0: Working with the Data Access Layer.

 

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>

NHibernate 3.0 Cookbook Get solutions to common NHibernate problems to develop high-quality performance-critical data access applications
Published: October 2010
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:
        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:


NHibernate 3.0 Cookbook Get solutions to common NHibernate problems to develop high-quality performance-critical data access applications
Published: October 2010
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:

About the Author :


Jason Dentler

Jason Dentler grew up in the small Texas town of Mission Valley. He started tinkering with computers as a kid in the late 1980s, and all these years later, he hasn't stopped. He's worked in a few different industries. Currently, he builds really awesome software in higher education. He's an Eagle Scout and a graduate of the University of Houston – Victoria.

Books From Packt


NHibernate 2 Beginner's Guide
NHibernate 2 Beginner's Guide

WCF 4.0 Multi-tier Services Development with LINQ to Entities
WCF 4.0 Multi-tier Services Development with LINQ to Entities

ASP.NET 3.5 Application Architecture and Design
ASP.NET 3.5 Application Architecture and Design

Microsoft Windows Communication Foundation 4.0 Cookbook for Developing SOA Applications
Microsoft Windows Communication Foundation 4.0 Cookbook for Developing SOA Applications

.NET Compact Framework 3.5 Data Driven Applications
.NET Compact Framework 3.5 Data Driven Applications

Microsoft Azure: Enterprise Application Development
Microsoft Azure: Enterprise Application Development

Applied Architecture Patterns on the Microsoft Platform
Applied Architecture Patterns on the Microsoft Platform

Microsoft Windows Workflow Foundation 4.0 Cookbook
Microsoft Windows Workflow Foundation 4.0 Cookbook


Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software