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!