I18N with EF Code First – Part II

In the previous post of this series I introduced some possible approaches to I18N using EF code first, and ended up with a translation-table-per-entity approach. In this post I’ll cover the first of the two solutions I found to implement it.

Since we’re translating some entity, our entity types need a collection of translations, which will contain a translation instance for each language/culture:

    public class SomeEntity
        public int Id { get; set; }
        public string NonLocalizableProp { get; set; }

        public virtual ICollection<SomeEntityTranslation> Translations { get; set; }

That’s pretty straightforward. What I’m really concerned about is the definition of the translation entity. It would be nice if we got two features:

  • Guarantee that each entity contains at most one translation for each language/culture;
  • Cascade delete, to automatically remove all the translations of an entity being deleted.
The first can be achieved by using some kind of weak entities (aka parent/children) relationship between the translations and the entity being translated. To that end, the key of the translation is composed by the id (key) of the entity being translated plus the language’s id (key). The second feature is accomplished by defining a one-to-many relationship with a required multiplicity on the one edge (“every translation must be associated to a translated entity“). When processing this type of associations, EF’s default behavior is to define on cascade delete on the translation’s foreign key to the translated entity table.
This gets clearer with some code! Following the principles above, we can define a generic base class for our translations:
  • TEntityKey is the type of the key of the entity being translated (usually some int or long id);
  • TEntity is the type of the entity being translated.
    public class EntityTranslationBase<TEntityKey, TEntity>
        [Key, Column(Order = 0), ForeignKey("Entity")]
        public TEntityKey EntityKey { get; set; }

        [Key, Column(Order = 1), ForeignKey("Language")]
        public int LanguageId { get; set; }

        public virtual TEntity Entity { get; set; }

        public virtual Language Language { get; set; }
The Key and Column attributes on the EntityKey and LanguageId properties are used to define the composite key. But we still need to tell EF that those should be foreign keys to the entity being translated and to the language, respectively. To that end, two additional properties (Entity and Language) are defined, which tell EF that the translation has associations with those two entities. Furthermore, we use the ForeignKey attribute to identify the columns that should be used as foreign keys on those associations. This way we have a composite key and appropriate foreign keys. The last step is to mark the Entity property as Required, to cause the default on cascade delete.
Going back to our sample some entity, the translation would be:
    public class SomeEntityTranslation : EntityTranslationBase<int, SomeEntity>
        public string LocalizableProp1 { get; set; }
        public string LocalizableProp2 { get; set; }
And that’s it! No additional configuration needed: all those properties and generic arguments provide EF with the needed information to establish the associations. In the next post I’ll present an alternative, which is cleaner on the types definition but will required some code when configuring the model.

3 thoughts on “I18N with EF Code First – Part II

  1. Pingback: I18N with EF Code First – Part III « Luís Gonçalves

  2. Is it possible to define this aproach with the fluent API instead of metadata attributes? I´d like to define for each translation the field name for the EntityKey and not to fixed it to “EntityKey”.

    • And you’d leave the remaining properties on the TranslationBase? I guess it is possible, since the fluent API has methods to configure all the features in use. But if the “EntityKey” property is moved to a derived class, that both the “Key” with order and the “ForeingKey” would have to be set via fluent API. Check out part III of this series for an approach that relies more on fluent API configuration.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s