Search code examples
c#nhibernateormnhibernate-mappingusertype

NHibernate Mapping - UserType with a column being re-used?


I basically have a Money value type, which consists of Amount and Currency. I need to map multiple Money values into a table, which has multiple fields for amount, but only one currency. In order words, I have the table:

Currency     Amount1    Amount2
===============================
USD          20.00      45.00

Which needs to be mapped to a class with 2 Money values (which can logically never have different currencies):

class Record
{ 
    public Money Value1 { get; set; }
    public Money Value2 { get; set; }
}

struct Money 
{
    public decimal Amount { get; set; }
    public string Currency { get;set; }
} 

(Example is a bit simplified)

The table schema cannot be changed. I'm happy to implement an IUserType, if needed, but I cannot figure out how to access the Currency column for both values.

How do I map this using NHibernate ?


Solution

  • It is not possible.

    If you create a Money UserType the purpose of the type is to combine two primitives into a new single data type. That data type is now a single atomic unit. Because the UserType is considered an atomic type both columns must be present for each Money value mapped. The rule NHibernate is enforcing is in fact semantically correct. You have two Money values. Therefore you must have two currencies - they cannot share one because a single Money type has a Currency and Amount and that is one atomic data unit now, it cannot be split up or shared.

    You want to at times treat currency as variable, and therefore needing its own column, and at other times as fixed so that multiple UserTypes can all share a column. Even if that were valid, which it is not based on how a Money is defined, how would NHibernate know what you wanted to do when? The system cannot just know what you want at any given time. You would now need to also save additional data with the Money type that specified how any given value was intended to be used.

    Bottom line, your columns as is cannot be mapped as a UserType. If you cannot change the schema, the only way you can do this is to map all three columns as normal primitives and then construct (and deconstruct) a Money type in your application code.

    And for what ends? I am really surprised that even someone like Fowler suggests this approach as some kind of "best practice" without really considering the actual details. The fact is most data is a set where the currency is dictated for a row by some often implicit or external factor such as country of origin or country where a business operates etc etc.

    The occasions where you might actually even need currency you often have so much other baggage that arises from multiple currencies such as current exchange rates etc that having the currency as part and parcel of a Money type is not even that helpful. It is a convenience, but for data which is very inconvenient to work with and cannot be made otherwise. Most of the time currency is fixed and usually can even be inferred. So a Money type in typical cases either cannot come close to what you really need - a conversion, or it is simply unnecessary information. With few exceptions the Money type becomes just a dingleberry on the ass of the application.

    Before you spend a lot of time trying to implement something for possibly little or no other reason than somehow people have begun to call this "best practice", ask yourself are you ever even going to need to use it for anything?