Mapping translations in NHibernate

Posted by Siim on February 24th, 2010

Some time ago I wrote about translations. Now was the time to actually implement that feature. Because I don’t have any default language which is always present on an object, I had somehow create it virtually, so in case of translation is not found in current language, the default one (for a given object) will be used. I decided that the first language for an object (that is, the language in which the object was created), is used as a default one.

So my data model looks like this. Firstname_id and lastname_id both refer to the translation table as foreign key relations.

data_model_loc

All the objects were mapped as entities and I was trying to map relation between Translation and it’s Localizations as an ordered list. But I soon discovered that ordered collections don’t support bi-directional associations natively. I had a bi-directional relation between Translation and Localizations. So I had to do index handling myself. For that I created a Index property to Localization which is mapped to order_index column. Property looks like this:

public virtual int Index
{
    get
    {
        return Translation.IndexOf(this);
    }
    private set
    { }
}

And Translation object looks like this:

public abstract class Translation
{
    private IList<Localization> _localizations;
    public Translation()
    {
        _localizations = new List<Localization>();
    }
    public abstract string Context { get; }
    public virtual IEnumerable<Localization> Localizations
    {
        get { return _localizations; }
    }
    public virtual int IndexOf(Localization localization)
    {
        return _localizations.IndexOf(localization);
    }
    public virtual string DefaultValue
    {
        get
        {
            var loc = _localizations.FirstOrDefault();
            return loc != null ? loc.Value : null;
        }
    }
    public virtual string GetCurrentValue(Language language)
    {
        return this[language] ?? DefaultValue;
    }
    public virtual string this[Language language]
    {
        get
        {
            var localization = _localizations.SingleOrDefault(x => x.Language.Locale == language.Locale);
            return localization == null ? null : localization.Value;
        }
        set
        {
            var localization = _localizations.SingleOrDefault(x => x.Language.Equals(language));
            if (localization != null && !string.IsNullOrEmpty(value))
            {
                localization.Value = value;
            }
            else if (localization != null)
            {
                RemoveLocalization(localization);
            }
            else if (!string.IsNullOrEmpty(value))
            {
                AddLocalization(language, value);
            }
        }
    }
    private void RemoveLocalization(Localization localization)
    {
        _localizations.Remove(localization);
    }
    private void AddLocalization(Language language, string value)
    {
        var localization = new Localization(language, value) { Translation = this };
        _localizations.Add(localization);
    }
}

As you can see, I added indexer property to Translation to manipulate with localizations conveniently. Although I’m not sure if using an object as an indexer has any drawbacks later on… Thoughts welcome.

You may have also noticed abstract Context property on Translation. It isn’t strictly required, but I found it useful in my implementation. By using this I can conveniently ask all the translations for person name or for product name, for example. This is useful when users need to translate all product names in a batch, so I can display them on a single form.

And here are example NHibernate mappings:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<!-- Translation -->
<class abstract="true" name="Translation" table="[translation]">
    <id name="Id" access="property" column="translation_id">
        <generator class="identity" />
    </id>
    <discriminator column="context"/>
    <list name="Localizations" cascade="all-delete-orphan" fetch="join" access="field.camelcase-underscore" inverse="true">
        <key column="translation_id" />
        <index column="order_index" />
        <one-to-many class="Localization" />
    </list>
    <subclass discriminator-value="PersonFirstName" name="PersonFirstNameTranslation">
    </subclass>
    <subclass discriminator-value="ProductName" name="ProductNameTranslation">
    </subclass>
    <!-- etc -->
</class>
<!-- Localization -->
<class name="Localization" table="localization">
    <id name="Id" access="property" column="localization_id">
        <generator class="identity" />
    </id>
    <many-to-one name="Translation" class="Translation" column="translation_id" not-null="true" />
    <many-to-one name="Language" class="Language" column="language_id" not-null="true" />
    <property name="Value" column="value"/>
    <property name="Index" column="order_index" type="int" update="true" insert="true" />
</class>
<!-- Person -->
<joined-subclass name="Person" extends="Party" table="person">
    <key column="party_id" />
    <property name="PersonalCode" column="personal_code" />
    <property name="DateOfBirth" column="birth_date" access="field.camelcase-underscore" />
    <many-to-one name="FirstName" column="firstname_id" class="Translation" fetch="join"
        access="property" cascade="all-delete-orphan" not-null="true"/>
    <many-to-one name="LastName" column="lastname_id" class="Translation" fetch="join"
        access="property" cascade="all-delete-orphan" not-null="true"/>
</joined-subclass>
</hibernate-mapping>

And storing a new object with translations is simple as this:

var person = new Person();
person.PersonalCode = "12345670";
// languages is IList<Language>
foreach (var language in languages)
{
    person.FirstName[language] = "First name: " + language.Name;
    person.LastName[language] = "Last name: " + language.Name;
}
session.Save(person);

This solution has it’s own drawbacks also, but I found that it’s best for my needs. Currently translations are not reusable. By that I mean when one term is used in multiple places, it has to be maintained separately on each instance. Of course, I can make user to choose from existing translations, but it seems to me that this makes things overly complicated. But again, it depends on the exact context :)

Repository and Query Object

Posted by Siim on November 19th, 2009

There has been some discussion over the topic already and there are different opinions. I have been using Repository model all the way so far. I have implemented generic base repository for all common operations and when there are some specific needs (view specific query for example!) for some types then I’ll create specific repository. I know that repositories should be created only for aggregate roots but there I have encountered a little problem. View needs to display data from different entities (aggregates or not) so I cannot always follow the principle of repository per aggregate root. So this poses a problem here – repositories are cluttered with view specific needs. And moreover – repository methods are decorated with sorting and paging parameters which definitely shouldn’t be there. So I decided to change that.

There have been some discussion about using multiple models of the domain for different purposes. For example different kind of a model for reporting purposes. It can even be different kind of data store for that model. Udi and Ayende (didn’t find exact posts anymore) have blogged about that before. Complex views with special needs can be also considered as a way of reporting. In my case, using wholly different model for a view is kind of a overkill so I try to leverage the problem by using query objects separated from the repository. They don’t use repositories by themselves so query objects can be viewed as "specific repositories for views" and they live only in presentation layer. And they don’t operate with domain objects, only with view specific objects like DTOs, filtering specifications. By using this kind of approach, writing complex view specific queries should be pretty easy. I’m not tied to NHibernate criteria, I can use HQL or even ADO.NET. Currently I’m using HQL which returns directly DTOs but I’m thinking of giving a try to LINQ for NHibernate.

When using repositories, I used Expressions to represent sorting and filtering specification. But this seems now overly complex when considering that there were some heavy mappings from string format to Expression format. So now, when it’s only a presentation concern, I can use plain strings, just like they come from the view and do the proper mappings in the query object itself. Like they say – KISS.


Copyright © 2007 Siim Viikman's blog.