NHibernate: Localization by swapping the column based on user culture
I have read few good articles about NHibernate localization but nothing seems to fit this scenario very well. Our DB stores localized info with the following pattern:
TABLE NAME: PRODUCT
COLUMNS:
ID
PRODUCT_CODE
PRODUCT_NAME
DESCRIPTION1
DESCRIPTION2
DESCRIPTION3
The fields DESCRIPTION1, DESCRIPTION2 and DESCRIPTION3 contain the localized description of the product. There is always exactly three localized strings and the number always refers to the same culture (for example 3 is English). In some other tables we have columns like NAME1, NAME2 and NAME3.
I would like to use (Fluent) NHibernate to map to these columns in a such way that the model-class would contain only one property called "Description" and it would conta开发者_开发百科in the value obtained from the correct field, based on the current thread's culture. There seems to be multiple extension points for NHibernate but which would be the best one?
Dynamically mapping string property to the correct column?
In my dream world I would have the Description-field as a String.
public class Product
{
/* ... */
public virtual string Description { get; set; }
}
And in the ClassMap I would somehow tell to Fluent NHibernate that the correct column name is evaluated on runtime:
public class ProductMap : ClassMap<Product>
{
public ProductMap()
{
/* ... */
MapAsLocalized(x => x.Description);
}
}
When a query is executed with this mapping, I would like my code automatically to check the thread's culture and select a correct column based on it. For example, if the culture is en-US, the query would fetch the value from DESCRIPTION3-column into the Description-property.
Custom IUSERTYPE?
But if this not possible I could settle to a custom LocalizedString-class which would allow me to define the Product-class like this:
public class Product
{
/* ... */
public virtual LocalizedString Description { get; set; }
}
And then get the localized value for example like this:
// Product prod;
// Get product from db
Console.WriteLine(prod.Description.Value);
In the LocalizedString-example the idea is that the dev can still create the map without having to create a mapping to every DESCRIPTION-column:
public class ProductMap : ClassMap<Product>
{
public ProductMap()
{
/* ... */
Map(x => x.Description);
}
}
And when a query is executed with this map, it returns the values of DESCRIPTION1, DESCRIPTION2 and DESCRIPTION3 all inside the the LocalizedString (which is internally a collection of some sorts).
Any recommendations on how to implement either the solution 1 or solution 2? Or even both?
you can go with a usertype
public class LocalizationUserType : ImmutableUserType
{
public override object NullSafeGet(IDataReader rs, string[] names, object owner)
{
switch(Languageprovider.GetLanguage())
{
case language1:
return NHibernateUtil.String.NullSafeGet(cmd, value, index);
case language2:
return NHibernateUtil.String.NullSafeGet(cmd, value, index + 1);
...
}
return null;
}
public override void NullSafeSet(IDbCommand cmd, object value, int index)
{
switch(Languageprovider.GetLanguage())
{
case language1:
NHibernateUtil.String.NullSafeSet(cmd, value, index);
break;
case language2:
NHibernateUtil.String.NullSafeSet(cmd, value, index + 1);
break;
...
}
}
public override Type ReturnedType
{
get { return typeof(String); }
}
public override SqlType[] SqlTypes
{
get { return new[]
{
SqlTypeFactory.GetString(1000),
SqlTypeFactory.GetString(1000),
SqlTypeFactory.GetString(1000),
}; }
}
}
ImmutableUserType is a baseclass, implementing some IUserType methods. I can send the implementation if you want
And usage:
public class ProductMap : ClassMap<Product>
{
ProductMap
{
...
Map(p => p.Name)
.Columns.Add("name1", "name2", "name3")
.CustomType<LocalizationUserType>();
...
Map(p => p.Description)
.Columns.Add("desc1", "desc2", "desc3")
.CustomType<LocalizationUserType>();
While NHibernate does provide a facility to modify mappings at run time. It would severely hamper performance for NHibernate to rebuild the session factory every time you want to do a query. I would recommend using your second idea. You don't even need a custom IUserType. You can map it as a component.
Component(x => x.Description, c =>
{
c.Map(d => d.Description1);
...
});
Alternatively you can map each of those descriptions to a backing field in your Product class and provide a public Description property that selects the appropriate field at run time.
精彩评论