Sometimes you will need more complex types to be stored in the database, such as currencies. In this recipe, we will show you how to implement a money type and the corresponding composite user type.
MoneyExample
.NHibernate
package using the NuGet Package Manager Console by executing the following command:Install-Package NHibernate
Money
class:public struct Money : IEquatable<Money> { public decimal Amount { get; } public string Currency { get; } public Money(decimal amount, string currency) { Amount = amount; Currency = currency; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; return obj is Money && Equals((Money) obj); } public bool Equals(Money other) { return Amount == other.Amount && string.Equals(Currency, other.Currency); } public override int GetHashCode() { unchecked { return (Amount.GetHashCode()*397) ^ Currency.GetHashCode(); } } public static bool operator ==(Money left, Money right) { return left.Equals(right); } public static bool operator !=(Money left, Money right) { return !left.Equals(right); } }
ICompositeUserType
named MoneyUserType
using the following code:public class MoneyUserType : ICompositeUserType { public object GetPropertyValue(object component, int property) { var money = (Money) component; switch (property) { case 0: return money.Amount; case 1: return money.Currency; default: throw new NotSupportedException(); } } public void SetPropertyValue(object component, int property, object value) { throw new InvalidOperationException("Money is an immutable object. SetPropertyValue isn't supported."); } public new bool Equals(object x, object y) { if (ReferenceEquals(x, y)) return true; if (ReferenceEquals(x, null)) return false; if (ReferenceEquals(y, null)) return false; return x.Equals(y); } public int GetHashCode(object x) { var moneyX = (Money) x; return moneyX.GetHashCode(); } public object NullSafeGet(IDataReader dr, string[] names, ISessionImplementor session, object owner) { var val = (decimal) NHibernateUtil.Decimal.NullSafeGet(dr, names[0], session, owner); var currency = (string) NHibernateUtil.String.NullSafeGet(dr, names[1], session, owner); return new Money(val, currency); } public void NullSafeSet(IDbCommand cmd, object value, int index, bool[] settable, ISessionImplementor session) { if (value == null) return; var money = (Money) value; var amount = money.Amount; var currency = money.Currency; NHibernateUtil.Double.NullSafeSet(cmd, amount, index, session); NHibernateUtil.String.NullSafeSet(cmd, currency, index + 1, session); } public object DeepCopy(object value) { var money = (Money) value; return new Money(money.Amount, money.Currency); } public object Disassemble(object value, ISessionImplementor session) { return DeepCopy(value); } public object Assemble(object cached, ISessionImplementor session, object owner) { return DeepCopy(cached); } public object Replace(object original, object target, ISessionImplementor session, object owner) { throw new NotImplementedException(); } public string[] PropertyNames { get { return new[] {"Amount", "Currency"}; } } public IType[] PropertyTypes { get { return new IType[] {NHibernateUtil.Double, NHibernateUtil.String}; } } public Type ReturnedClass { get { return typeof(Money); } } public bool IsMutable { get { return false; } } }
<?xml version="1.0" encoding="utf-8"?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="MoneyExample" namespace="MoneyExample"> <typedef name="money" class="MoneyExample.MoneyUserType, MoneyExample"> </typedef> <class name="Account"> <id name="Id"> <generator class="guid.comb" /> </id> <property name="Name" not-null="true" /> <property name="Balance" not-null="true" type="money" > <column name="Balance_Amount" not-null="true" /> <column name="Balance_Currency" length="3" not-null="true" /> </property> </class> </hibernate-mapping>
As we saw in the Creating an encrypted string type recipe, we can set the type attribute on a property to specify a class used for converting data between our application and the database. We will use this to store our complex user type. The composite user type is a type that can have multiple columns in the database.
Our MoneyUserType
type implements ICompositeUserType
, NHibernate's interface for defining custom composite types. When implementing ICompositeUserType
, NullSafeGet
is responsible for reading data from the ADO.NET data reader and returning an appropriate object. NullSafeSet
takes some value, in this case, our Amount
and Currency
, and sets a parameter on the ADO.NET command
. The PropertyTypes
property returns an array representing the types of each database field used to store this user type. In our case, we have two fields of decimal and string types. The PropertyNames
property returns an array representing the names of each database field used to store this user type. The GetPropertyValue
method returns the value of the property by its index. Indexes should match the indexes in the PropertyTypes
and PropertyNames
arrays. SetPropertyValue
method throws InvalidOperationException
because our user type is immutable as it is a value type (struct
). The ReturnedType
property returns our custom type.
Instead of implementing your own money type, you can use the well-written and well-tested library, NMoneys, sources of which you can find at https://github.com/dgg/nmoneys.
3.133.133.233