Recently I needed to create a string combined from several columns in the database into a single field. More precisely, I had an User
entity with a FirstName
and Surname
properties and I needed the FullName
property for filling the data into a ViewModel. The entity itself has many properties and its hydration is quite slow.
public class User : EntityBase
{
public virtual string FirstName {get;set;}
public virtual string Surname {get;set;}
public virtual string FullName
{
get { return string.Format("{0} {1}", FirstName, Surname); }
}
// many other properties
}
Querying a view model
I wanted to create a view model in single linq query. The query will get from database only the fields that are necessary, not whole entities (e.g. only Title and).
var viewModelQuery =
from post in session.Query<Post>()
where post.Id == postId
select new PostViewModel
{
Title = post.Title,
Text = post.Body,
CreatedBy = string.Format("{0} {1}",
post.CreatedBy.FirstName, post.CreatedBy.Surname)
};
The corresponsing query looks like this – notice it is a single query that gets only what is needed, no unnecessary properties of the User
or the Post
are fetched. This is the reason why I really like the NHibernate Linq provider as opposed to Criteria API or QueryOver.
select
post0_.Title as col_0_0_,
post0_.Body as col_1_0_,
user1_.first_name as col_2_0_,
user1_.surname as col_3_0_
from
Post post0_
left outer join
User user1_
on post0_.CreatedBy=user1_.Id
where
post0_.Id=@p0;
However, there is a flaw: The string.Format
creation of the CreatedBy. I would like to use FullName property, but that is not possible, because it is not mapped and using CreatedBy = post.CreatedBy.FullName
would throw a mapping exception.
Querying full name
We can get around that using a formula in the mapping that has same result as the property in the entity class:
<class name="User">
<id name="Id">
<generator class="increment" />
</id>
<property name="FirstName" column="first_name" />
<property name="Surname" column="surname" />
<property name="FullName" access="readonly" formula="(first_name || ' ' || surname)" />
</class>
Notice use of readonly
access. It was implemented in the NH-1621. It is an accessor used for querying in the database and thanks to that we can use it directly in the query, the prerequisites are existing property with getter (no need for setter) and semantically same as the column in the database. In our case, the property in not mapped to a column, but to a formula, but result is same: We can use it directly in the query:
var viewModelQuery =
from post in session.Query<Post>()
where post.Id == postId
select new PostViewModel
{
Title = post.Title,
Text = post.Body,
CreatedBy = post.CreatedBy.FullName
};
var viewModel = viewModelQuery.Single();
Assert.AreEqual("Post title", viewModel.Title);
Assert.AreEqual("Text of the post", viewModel.Text);
Assert.AreEqual("John Smith", viewModel.CreatedBy)
The SQL query uses formula instead of two separate columns and as before it does it without getting unnecessary properties:
select
post0_.Title as col_0_0_,
post0_.Body as col_1_0_,
(user1_.first_name || ' ' || user1_.surname) as col_2_0_
from
Post post0_
left outer join
User user1_
on post0_.CreatedBy=user1_.Id
where
post0_.Id=@p0
Now we can safely add a middle name or reverse order of full name simply by modifying a formula in the mapping file and the getter property and the change will appear everywhere as opposed to modifying the string.Format
method.