I want my entity's text properties being saved in several languages. For this I just want to store a FK to the TextContent table (which basically contains only a key) and a separate Translation table which contains a row for each translation.
Here is my database model
And my corresponding entities
public sealed class Translation : IEquatable<Translation>
{
public static bool operator ==(Translation left, Translation right) => Equals(left, right);
public static bool operator !=(Translation left, Translation right) => !Equals(left, right);
// ORM-only constructor
private Translation() { }
public Translation(int languageId, string text)
{
_language = languageId;
_text = text;
}
public bool Equals(Translation other) ...
public override bool Equals(object obj) ...
public override int GetHashCode() ...
public int LanguageId => _language;
public string Text => _text;
private readonly int _languageId;
private readonly string _text;
}
public class TextContent
{
// ORM-only constructor
protected TextContent() { }
public TextContent(int Id, List<Translation> translations)
{
_id = Id;
_originalLanguage = translations.First().LanguageId;
_originalText = translations.First().Text;
_translations = translations;
}
public virtual int Id => _id;
public virtual int OriginalLanguageId => _originalLanguage;
public virtual string OriginalText => _originalText;
public virtual IList<Translation> Translations => _translations;
private readonly int _id;
private readonly int _originalLanguageId;
private readonly string _originalText;
private readonly IList<Translation> _translations;
}
public partial class Product
{
// ORM-only constructor
protected Product() { }
public Product(int Id, TextContent name, TextContent description)
{
_id = Id;
_nameId = name.Id;
_name = name;
_descriptionId = description.Id;
_description = description;
}
public virtual int Id => _id;
public virtual TextContent Name => _name;
public virtual TextContent Description => _description;
private int _id;
private int _nameId;
private TextContent _name;
private int _descriptionId;
private TextContent _description;
}
The mappings I tried
public class TextContentMapping : ClassMapping<TextContent>
{
public TextContentMapping()
{
Table("TextContent");
Id(content => content.Id);
Property(content => content.OriginalLanguage);
Property(content => content.OriginalText);
Bag(
content => content.Translations,
mapper =>
{
mapper.Table(nameof(Translation));
mapper.Cascade(Cascade.All);
mapper.Key(
keyMapper =>
{
keyMapper.Column(columnMapper => columnMapper.Name("TextContentId"));
keyMapper.NotNullable(true);
});
},
relation => relation.Component(
mapper =>
{
mapper.Property(translation => translation.LanguageId);
mapper.Property(translation => translation.Text);
}));
}
}
public class ProductMapping : ClassMapping<Product>
{
public ProductMapping()
{
Table(nameof(Product));
Id(product=> product.Id, mapper => mapper.Access(Accessor.Field));
Property(
"_nameId",
mapper =>
{
mapper.Column("NameId");
mapper.Access(Accessor.Field);
mapper.NotNullable(true);
});
Property(
"_descriptionId",
mapper =>
{
mapper.Column("DescriptionId");
mapper.Access(Accessor.Field);
mapper.NotNullable(true);
});
OneToOne(
product => product.Name,
mapper =>
{
mapper.Constrained(true);
mapper.Cascade(Cascade.All);
mapper.Access(Accessor.Field);
mapper.Class(typeof(TextContent));
});
OneToOne(
product => product.Description,
mapper =>
{
mapper.Constrained(true);
mapper.Cascade(Cascade.All);
mapper.Access(Accessor.Field);
mapper.Class(typeof(TextContent));
});
}
}
As you may guess the insert works but the select doesn't as my current mapping don't provide a link between _nameId
and TextContent Name
entity ?
Is there a way to do it ? If not what would be a better mappings and domain entities that correspond to my database model?
OneToOne mapping is when the other side has the foreign key. It should be ManyToOne. Using these classes
public class TextContent
{
// ORM-only constructor
protected TextContent() { }
public TextContent(int languageId, string text) : this()
{
OriginalLanguageId = languageId;
OriginalText = text;
Translations = new Dictionary<int, string>();
}
public virtual int Id { get; protected set; }
public virtual int OriginalLanguageId { get; protected set; }
public virtual string OriginalText { get; protected set; }
public virtual IDictionary<int, string> Translations { get; protected set; }
}
public class Product
{
// ORM-only constructor
protected Product() { }
public Product(TextContent name, TextContent description)
{
Name = name;
Description = description;
}
public virtual int Id { get; protected set; }
public virtual TextContent Name { get; protected set; }
public virtual TextContent Description { get; protected set; }
}
and these mappings
public class TextContentMapping : ClassMapping<TextContent>
{
public TextContentMapping()
{
Table("TextContent");
Id(content => content.Id, m => m.Generator(Generators.Native));
Property(content => content.OriginalLanguageId);
Property(content => content.OriginalText);
Map(
content => content.Translations,
mapper =>
{
mapper.Table("Translation");
mapper.Cascade(Cascade.All);
mapper.Key(
keyMapper =>
{
keyMapper.Column(columnMapper => columnMapper.Name("TextContentId"));
keyMapper.NotNullable(true);
});
},
keymapping => keymapping.Element(m => m.Column("LanguageId")),
elementMapping => elementMapping.Element(m => m.Column("Text"))
);
}
}
public class ProductMapping : ClassMapping<Product>
{
public ProductMapping()
{
Table(nameof(Product));
Id(product => product.Id, m => m.Generator(Generators.Native));
ManyToOne(
product => product.Name,
mapper =>
{
mapper.Column("NameId");
mapper.Cascade(Cascade.All);
mapper.NotNullable(true);
});
ManyToOne(
product => product.Description,
mapper =>
{
mapper.Column("DescriptionId");
mapper.Cascade(Cascade.All);
mapper.NotNullable(true);
});
}
}
the following code works
session.Save(new Product(new TextContent(1, "someText") { Translations = { { 2, "translated Text" } } }, new TextContent(1, "some description")));
session.Flush();
session.Clear();
var p = session.Query<Product>().ToList();
var s = p[0].Name.Translations[2];