Creating a money type

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.

How to do it…

  1. Create a new class library project named MoneyExample.
  2. Install the NHibernate package using the NuGet Package Manager Console by executing the following command:
    Install-Package NHibernate
    
  3. Create the following 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);
      }
    } 
  4. Add an implementation of 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; }
      }
    } 
  5. Add a mapping document with the following XML. Don't forget to set the Build Action to Embedded Resource:
    <?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>

How it works…

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.

There's more…

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.

See also

  • Creating an encrypted string type
  • Mapping a component
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.82.253