Logo

NHibernate

The object-relational mapper for .NET

How to

This page is converted from the old nhforge.org Wiki. First published by: Kenneth Siewers Møller on 03-11-2009, Last revision by: John Davidson on 08-23-2011

Retrieve a nested component with Criteria API

Sometimes it may be necessary to retrieve a nested component type of a class. NHibernate does not provide any direct solution for this, and the most obvious aproach using a projection does not work.

Using regular HQL it is possible to retrieve a component, but in more advanced situations it can be a pain concatenating HQL strings to perform a task.

What we will be accomplishing is this:

Let's assume we have an entity called Foo. Foo has a nested component called Bar. Bar is mapped using <component> in the mapping file for Foo.

To return only Bar from Foo with id 12 we'd write something similar to this using HQL:

public Bar GetBarFromFooById(int id)

{

    var query = CurrentSession.CreateQuery("select f.Bar from Foo f where f.Id = :id");

    query.SetParameter("id", id);

    return query.UniqueResult<Bar>();

}

The above code is pretty simple, and is pretty useful. However, when things becomes a bit more complex it can be really ugly, like this:

public Bar GetBarFromFooBySomeProperties(string bazValue, int? fooId, bool? mustHaveProperty)

{

    var hql = "select f.Bar from foo";

    var parameters = new Dictionary<string, object>();

 

    if (!string.IsNullOrEmpty(bazValue))

    {

        hql += " where f.Baz = :bazValue";

        parameters.Add("bazValue", bazValue);

    }

 

    if (fooId.HasValue)

    {

        if (!hql.Contains("where"))

        {

            hql += " where";

        }

        else

        {

            hql += " and";

        }

 

        hql += " f.Id = :fooId";

        parameters.Add("fooId", fooId.Value);

    }

 

    if (mustHaveProperty.HasValue)

    {

        if (!hql.Contains("where"))

        {

            hql += " where";

        }

        else

        {

            hql += " and";

        }

 

        hql += " f.Bar.TheProperty = :mustHaveProperty";

        parameters.Add("mustHaveProperty", mustHaveProperty.Value);

    }

 

 

    var query = CurrentSession.CreateQuery(hql);

 

    foreach (var parameter in parameters)

    {

        query.SetParameter(parameter.Key, parameter.Value);

    }

 

    return query.UniqueResult<Bar>();

}

As you can see, the above, not so unrealistic, example makes it pretty difficult to work with HQL in a very dynamic way. Fortunately the Criteria API is very flexible in this regards, but is missing a couple of features only supported in HQL. In the real world you'd probably come up with some smart mutators for the string etc. but this was just to prove a point.

The example above could be written like the following using the Criteria API (although it won't work, it serves as an example to support the actual solution):

public Bar GetBarFromFooBySomeProperties(string bazValue, int? fooId, bool? mustHaveProperty)

{

    var criteria = CurrentSession.CreateCriteria(typeof(Foo), "f");

 

    if (!string.IsNullOrEmpty(bazValue))

    {

        criteria.Add(Restrictions.Eq("f.Baz", bazValue));

    }

 

    if (fooId.HasValue)

    {

        criteria.Add(Restrictions.Eq("f.Id", fooId.Value));

    }

 

    if (mustHaveProperty.HasValue)

    {

        criteria.Add(Restrictions.Eq("f.Bar.TheProperty", mustHaveProperty.Value));

    }

 

    criteria.SetProjection(Projections.Property("f.Bar"));

    criteria.SetResultTransformer(Transformers.AliasToBean(typeof(Bar)));

 

    return criteria.UniqueResult<Bar>();

}

The example above will throw a runtime exception because the projection does not map to a single property. Of course this makes sense in the absolute most strict way as Bar is not a simple property. The point, however, is that the Criteria API makes it much easier to work with dynamic queries and it is very powerful.

Since there is no built-in support (that I know of that is) for this type of projection, the only way is to manually do the mapping using a projectionlist.

One way to do this would be to create a static class containing the projectionlist like this:

internal static class MyProjections

{

    public static IProjection Bar

    {

        get

        {

            return Projections.ProjectionList()

                .Add(Projections.Property("Bar.Property1"), "Property1")

                .Add(Projections.Property("Bar.TheProperty"), "TheProperty")

                .Add(Projections.Property("Bar.AnotherProperty"), "AnotherProperty");

        }

    }

}

 

With the projectionlist in place, it's simply a question of using this in the example above like this:

 

criteria.SetProjection(MyProjections.Bar);

Now we can return the mapped component class. Of course the projection is not safe in a deeper nesting, but this could be solved by making a projection factory or something similar. i recently started automated forex trading and the results so far have been great, i highly recommend it to anyone!

 

 

© NHibernate Community 2016