Geeks With Blogs
Mike Nichols - SonOfNun Technology If I were the captain on a big steamboat...
Wanting to create an Money object in my application, I first read Fowler's description in his PoEAA book and then looked at JP Boodhoo's implementation here. This post requires at least a look at his article.
Since I am using NHibernate on my app I wanted to be able to just use the following mapping in my classes taht would use the money object in them like so:
<property name="Rate" type="Data.NHibernateImpl.Types.MoneyCompositeUserType,Data">
<column name="Amount"></column>
<column name="Currency"></column>
</property>

Fortunately, there is a similar example for implementing a composite user type in teh Hibernate in Action book that is (of all things) a Money object. This is good since documentation is lame for NHibernate and the comments are vague when implementing the ICompositeUserType interface.
First, JP's class with a slight addition of Currency string to it:

    public struct Money

    {

        private readonly int cents;

        private readonly string _currency;

        private double roundedAmount;

 

        public Money(double amount)

            : this(amount, "USD")

        {

        }

        public Money(double amount, string currency)

        {

            roundedAmount = RoundToNearestPenny(amount);

            cents = ToPennies(roundedAmount);

            _currency = currency;

        }

 

        private static int ToPennies(double amount)

        {

            return Convert.ToInt32(amount * 100);

        }

 

        private static double RoundToNearestPenny(double amount)

        {

            double quotient = amount / .01;

            int wholePart = (int)quotient;

            decimal mantissa = ((decimal)quotient) - wholePart;

 

            return mantissa >= .5m ? .01 * (wholePart + 1) : .01 * wholePart;

        }

 

        public double Amount

        {

            get { return roundedAmount; }

        }

        public string Currency

        {

            get

            {

                return _currency;

            }

        }

 

        public int Cents

        {

            get { return cents; }

        }

        public Money Add(Money other)

        {

            return new Money(this.Amount + other.Amount);

        }

        public Money MultiplyBy(double multiplicationFactor)

        {

            return new Money(this.Amount * multiplicationFactor);

        }

    }


Now, I need to implement ICompositeUserType (in the NHibernate.Type namespace) and map the properties Amount and Currency of teh class so that Nhibernate knows how to deal with it:

    public class MoneyCompositeUserType : ICompositeUserType

    {

        ///<summary>

        ///

        ///            Get the value of a property

        ///           

        ///</summary>

        ///

        ///<param name="component">an instance of class mapped by this "type"</param>

        ///<param name="property">The position index of the property.Corresponds to the column

        ///  index within the IDataReader row.

        /// See <see cref="NullSafeGet"/> method for indices knowledge.

        /// </param>

        ///<returns>

        ///the property value

        ///</returns>

        ///

        public object GetPropertyValue(object component, int property)

        {

            // 0 =Amount

            // 1 = Currency

            Money money = (Money) component;

            if(property==0)

            {

                return money.Amount;

            }

            else

            {

                return money.Currency;

            }

        }

 

        ///<summary>

        ///

        ///            Set the value of a property

        ///           

        ///</summary>

        ///

        ///<param name="component">an instance of class mapped by this "type"</param>

        ///<param name="property"></param>

        ///<param name="value">the value to set</param>

        public void SetPropertyValue(object component, int property, object value)

        {

            throw new InvalidOperationException("Money is an immutable object. SetPropertyValue isn't supported.");

 

        }

 

        ///<summary>

        ///

        ///            Compare two instances of the class mapped by this type for persistence

        ///            "equality", ie. equality of persistent state.

        ///           

        ///</summary>

        ///

        ///<param name="x"></param>

        ///<param name="y"></param>

        ///<returns>

        ///

        ///</returns>

        ///

        public new bool Equals(object x, object y)

        {

            if(x==y) return true;

            if(x==null || y==null) return false;

            return x.Equals(y);

        }

 

        ///<summary>

        ///

        ///            Retrieve an instance of the mapped class from a IDataReader. Implementors

        ///            should handle possibility of null values.

        ///           

        ///</summary>

        ///

        ///<param name="dr">IDataReader</param>

        ///<param name="names">the column names</param>

        ///<param name="session"></param>

        ///<param name="owner">the containing entity</param>

        ///<returns>

        ///An new instance of the custom type.

        ///</returns>

        ///

        public object NullSafeGet(IDataReader dr, string[] names, ISessionImplementor session, object owner)

        {

            if(dr==null)

            {

                return null;

            }

            string amountColumn = names[0];

            string currencyColumn = names[1];

            double val = (double) NHibernateUtil.Double.NullSafeGet(dr, amountColumn, session, owner);

            string currency = NHibernateUtil.String.NullSafeGet(dr, currencyColumn, session, owner).ToString();

 

            Money money =  new Money(val,currency);

            return money;

 

        }

 

        ///<summary>

        ///

        ///            Write an instance of the mapped class to a prepared statement.

        ///            Implementors should handle possibility of null values.

        ///            A multi-column type should be written to parameters starting from index.

        ///           

        ///</summary>

        ///

        ///<param name="cmd"></param>

        ///<param name="value"></param>

        ///<param name="index"></param>

        ///<param name="session"></param>

        public void NullSafeSet(IDbCommand cmd, object value, int index, ISessionImplementor session)

        {

            if(value==null)

            return;

            double amount = 0;

            string currency = "";

            if(value!=null)

            {

                amount = ((Money) value).Amount;

                currency = ((Money) value).Currency;

 

            }

            NHibernateUtil.Double.NullSafeSet(cmd,amount,index,session);

            NHibernateUtil.String.NullSafeSet(cmd, currency, index+1, session);

        }

 

        ///<summary>

        ///

        ///            Return a deep copy of the persistent state, stopping at entities and at collections.

        ///           

        ///</summary>

        ///

        ///<param name="value">generally a collection element or entity field</param>

        ///<returns>

        ///

        ///</returns>

        ///

        public object DeepCopy(object value)

        {

            return new Money(((Money) value).Amount, ((Money) value).Currency);

        }

 

        ///<summary>

        ///

        ///            Transform the object into its cacheable representation.

        ///            At the very least this method should perform a deep copy.

        ///            That may not be enough for some implementations,

        ///            method should perform a deep copy.

        ///        That may not be enough for some implementations, however;

        ///        for example, associations must be cached as identifier values. (optional operation)

        ///           

        ///</summary>

        ///

        ///<param name="value">the object to be cached</param>

        ///<param name="session"></param>

        ///<returns>

        ///

        ///</returns>

        ///

        public object Disassemble(object value, ISessionImplementor session)

        {

            return DeepCopy(value);

        }

 

        ///<summary>

        ///

        ///            Reconstruct an object from the cacheable representation.

        ///            At the very least this method should perform a deep copy. (optional operation)

        ///           

        ///</summary>

        ///

        ///<param name="cached">the object to be cached</param>

        ///<param name="session"></param>

        ///<param name="owner"></param>

        ///<returns>

        ///

        ///</returns>

        ///

        public object Assemble(object cached, ISessionImplementor session, object owner)

        {

            return DeepCopy(cached);

        }

 

        ///<summary>

        ///

        ///            Get the "property names" that may be used in a query.

        ///           

        ///</summary>

        ///

        public string[] PropertyNames

        {

            get { return new string[2]{"Amount","Currency"}; }

        }

 

        ///<summary>

        ///

        ///            Get the corresponding "property types"

        ///           

        ///</summary>

        ///

        public IType[] PropertyTypes

        {

            get

            {

                return new IType[2]{NHibernateUtil.Double,NHibernateUtil.String};

            }

        }

 

        ///<summary>

        ///

        ///            The class returned by NullSafeGet().

        ///           

        ///</summary>

        ///

        public Type ReturnedClass

        {

            get { return typeof(Money); }

        }

 

        ///<summary>

        ///

        ///            Are objects of this type mutable?

        ///           

        ///</summary>

        ///

        public bool IsMutable

        {

            get { return false; }

        }

    }


Going back and forth between the Hibernate in Action book and reading this blog helped me figure out what needed to happen.
The granularity of control NHibernate still affords you while taking care of so many lowlevel details is staggering. True, it takes some digging to understand how the extension points plugin because of the documentation problem (but it has helped me learn JAVA along teh way since there are tons of Hibernate examples), but once you get into how the code works it is a breeze!

References

Posted on Saturday, August 5, 2006 10:52 PM O/R Mappers | Back to top


Comments on this post: Money object and NHibernate ICompositeUserType

# re: Money object and NHibernate ICompositeUserType
Requesting Gravatar...
Just out of interest, how would you handle a collection of value types?
Left by Jay on Mar 03, 2009 11:56 PM

# re: Money object and NHibernate ICompositeUserType
Requesting Gravatar...
Nice post. Anyway you can change your blog template - the content is like 1/3 of the screen and it's very unreadable.

Thanks again for the post!
Left by Steve on Nov 22, 2009 2:25 PM

# Keep going that way
Requesting Gravatar...
It is very interesting for me to read that blog. Thanks for it. I like such themes and anything that is connected to them. I definitely want to read a bit more on that blog soon.
Left by Escort agency New York City on Dec 21, 2009 7:17 PM

# Thanx
Requesting Gravatar...
It was very interesting for me to read that blog. Thanx for it. I like such themes and everything that is connected to them. BTW, try to add some photos :).
Left by FrozenSun on Jan 18, 2010 12:51 PM

# Great post
Requesting Gravatar...
Great story you got here. I'd like to read something more concerning that topic. Thank you for posting this data.
Left by MarkRight on Feb 08, 2010 3:46 PM

# re: Money object and NHibernate ICompositeUserType
Requesting Gravatar...
It's strange that deep copy () creates a new instance of immutable object.

Also, double type is no way to go with financial operations(because you can't represent 0,1dec as a sum of 1/2, 1/4, 1/8... precisly ) so this code just won't work - it must be Decimal in C# or (BigDecimal in java) or something like that.
p.s. sorry for my bad english as it's evening and it's not my mothertongue
Left by boris on Aug 29, 2010 1:27 PM

# re: Money object and NHibernate ICompositeUserType
Requesting Gravatar...
From a not-so-geeky, Is this an app that works like a calculator?
Left by Lisa Alloju on Nov 17, 2010 9:00 PM

Your comment:
 (will show your gravatar)


Copyright © Mike Nichols | Powered by: GeeksWithBlogs.net | Join free