문제

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 ?

도움이 되었습니까?

해결책

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?

다른 팁

It's not possible because it doesn't make sense when you consider reading and writing entities.

Consider the case where Value1 is USD 20 and Value2 is GBP 40. How would you persist it?

What about changing your classes like this?

class Record
{ 
    public MoneyCollection Money { get; set; }
}

class MoneyCollection
{
    public MoneyCollection(string currency, params decimal[] amount1) { /*...*/ }
    public decimal[] Amount { get; private set; }
    public string Currency { get; private set; }
    public Money[] Money
    {
      get
      {
        return Amount.Select(x => new Money(Currency, x)).ToArray();
      }
    }
} 

class Money 
{
    public Money(decimal amount, string currency ) { /* ... */ }
    public decimal Amount { get; private set; }
    public string Currency { get; private set; }
}

Now you can write a user type for MoneyCollection.

Note: You need to make sure that the MoneyCollection has a constant number, or at least a maximal number of values, because you need to map it to a constant number of columns. Check this in the class itself.

I'm just a newbie only learning about and working with NHibernate for a few months now, but isn't it possible to make a complex usertype (IEnhancedUserType) which writes to multiple columns, whereas one column will be redundant?

Difficult to explain, but if you map a property in your entity to the currency column which takes care of read/write actions and you refer to the same column multiple times within the complex usertype mapping and turn off insert and update, thus making it a readonly column, wouldn't that work? I've been thinking of trying to make a usertype like this to see if it works (since i haven't tried it yet, I don't have any example code atm)

I know it is a few years late - but I have a solution.

private decimal _subTotal;
private decimal _shipping;
private CurrencyIsoCode _currency;

public virtual Money SubTotal
{
    get { return new Money(_subTotal, _currency); }
    set
    {
        _subTotal = value.Amount;
        _currency = value.CurrencyCode;
    }
}

public virtual Money Shipping
{
    get { return new Money(_shipping, _currency); }
    set
    {
        _shipping = value.Amount;
        _currency = value.CurrencyCode;
    }
}

Mapping:

Map(Reveal.Member<Basket>("_subTotal")).Column("SubTotal");
Map(Reveal.Member<Basket>("_shipping")).Column("Shipping");
Map(Reveal.Member<Basket>("_currency")).Column("Currency");
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top