Localizing NHibernate: Contextual Parameters
Please note that this is no longer supported behavior in NHibernate 2.1 and up. It was a hack to begin with, and it isn't guaranteed to continue working.
I can't think of a good name for this post, but it is a very cool discovery. First, let us examine the scenario, a customer of mine has bought a database of localized data, which had the following structure:
Now, what the customer wanted to be able to do is something like this:
Product product = session.Get<Product>(5);
productName.Text = product.Name;
And get the correct name for the user's culture. In addition to that, they didn't want to have to load the all the product names collection and get the name in memory. The database include several hundred thousnads of products, localized to several languagues, so this is a big perf concern.
I thought about this quite a bit, and was nearly at the point where I told them that it can't be done when I recalled that NHibernate 1.2 added filters capabilities. A quick testing proved that it is possible to do this with NHibernate, using the following approach:
First, we need to map the product, like this:
<class name='Product'>
<id name='Id'
column='id'>
<generator class='native'/>
</id>
<property name='Amount'/>
<property name='Size'/>
<property name='Name'
formula='(SELECT ProductNames.Name FROM ProductNames
WHERE ProductNames.Id = id and ProductNames.CultureId = :CultureFilter.CultureId)'/>
</class>
Please note the Name property, we map it using a formula, which is just a piece of SQL that we put in the mapping. The new thing here is that we can refer to :CultureFilter.CultureId in the formula. Where does it come from? As you can see above, we need this to be transparent to the developer when using the code.
The secret is in the following bit of mapping:
<filter-def name='CultureFilter'>
<filter-param name='CultureId' type='System.Int32'/>
</filter-def>
Here we define a filter, which takes parameters, usually a class will use the filter-def parameters to express a filter according to this parameters. But we don't want to filter the results, we want to just have it happen. It turns out to be that using the full name of the filter parameter is something that NHibernate understand anywhere, so...
//This is usually in Application_BeginRequest
session.EnableFilter("CultureFilter").SetParameter("CultureId", Thread.CurrentThread.CurrentCulture.LCID);
Product product = session.Get<Product>(5);
productName.Text = product.Name;
And you get exactly what you wanted! I was very pleased when I was able to come up with such an elegant solution :-D
Comments
Nice... what happens with 2nd level caching? How do you distinguish between the same object in different languages?
Good question, I do not know off hand the answer.
I don't think that it is taking into account the filters, but I can't tell for sure.
You can try it out and tell me if it doesn't work (via the NHibernate JIRA), we will fix it.
Comment preview