Using Type Converters

Type converters are supported, as long as the type converter class implements IUserType and it derives from System.ComponentModel.TypeConverter. The TypeConverter base class is required for the designer, the IUserType for working with NHibernate. To be able to load the type converter into the designer, the NHibernate.dll assembly has to be present in the folder where the Type Converter is loaded from as the IUserType type has to be loaded.

In previous versions, LLBLGen Pro shipped a native NHibernate type converter dll which was able to convert booleans into numerics and vice versa. It was compiled against NHibernate 2.1.2 and as the NHibernate dll had to be present in the type converter folder, this was a bit of a problem.

To overcome this, the complete sourcecode of this type converter is included below. It's recommended you compile the type converter from the sourcecode below with the NHibernate version you're using currently (e.g. 3.2 GA), and place it in the TypeConverters folder of the designer, together with the NHibernate dll, replacing the shipped one. At runtime, its enough to place the typeconverter dll into the bin folder of the executable.

Although NHibernate supports int to bool and char to bool conversions, the LLBLGen Pro designer requires an explicit conversion for these types, so even for these type conversions, a type converter is required. Enums are natively supported, so one doesnt need a type converter for enums in NHibernate.

The system type converters aren't enabled for NHibernate, as NHibernate's native conversions are query replacers and can fail in certain situations, hence the requirement of a specific IUserType implementing type.

BooleanNumeric type converter sourcecode

Below is the full sourcecode of the booleannumeric converter for NHibernate, implementing IUserType. Compile this class against the NHibernate dll you're using as described above. The sourcecode is licensed to you using the BSD2 license which means you can use it freely in commercial and open source applications.

//////////////////////////////////////////////////////////////////////
// Public Class definitions for the Type converter assembly of LLBLGen Pro.
// (c) 2002-2012 Solutions Design, all rights reserved.
// http://www.llblgen.com/
//////////////////////////////////////////////////////////////////////
// The sourcecode for this type converter is released as BSD2 licensed open source, so licensees and others can
// modify, update, extend or use it to write other type converters 
//////////////////////////////////////////////////////////////////////
// COPYRIGHTS:
// Copyright (c)2002-2012 Solutions Design. All rights reserved.
// 
// This LLBLGen Pro type converter is released under the following license: (BSD2)
// -------------------------------------------------------------------------
// Redistribution and use in source and binary forms, with or without modification, 
// are permitted provided that the following conditions are met: 
//
// 1) Redistributions of source code must retain the above copyright notice, this list of 
//    conditions and the following disclaimer. 
// 2) Redistributions in binary form must reproduce the above copyright notice, this list of 
//    conditions and the following disclaimer in the documentation and/or other materials 
//    provided with the distribution. 
// 
// THIS SOFTWARE IS PROVIDED BY SOLUTIONS DESIGN ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SOLUTIONS DESIGN OR CONTRIBUTORS BE LIABLE FOR 
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 
// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
//
// The views and conclusions contained in the software and documentation are those of the authors 
// and should not be interpreted as representing official policies, either expressed or implied, 
// of Solutions Design. 
//////////////////////////////////////////////////////////////////////
using System;
using System.ComponentModel;
using NHibernate.SqlTypes;
using NHibernate.UserTypes;
using System.Data;
using NHibernate;

namespace SD.LLBLGen.Pro.TypeConverters
{
    /// <summary>
    /// Implementation of a BooleanNumericConverter. This converter uses 'Boolean' as its core type and converts numeric values to and from the boolean.
    /// Any value other than 0 is seen as true, and 0 is seen as false.
    /// </summary>
    /// <remarks>This implementation is targeted to be used as a converter which is instantiated through code, not through TypeDescriptor.GetConverter.
    /// This means that this converter can be used in an attribute on a type, but the implementation can fail (but doesn't have to) in that 
    /// situation as it's not tested in that scenario. The core difference is that 'context' is ignored in the converter code.</remarks>
    [Description("Converter with as core type System.Boolean, for mapping a field with a .NET type System.Boolean onto an integer (int32) database field. For NHibernate, implements IUserType")]
    public class BooleanNumericConverterNHibernate : TypeConverter, IUserType
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="BooleanNumericConverterNHibernate"/> class.
        /// </summary>
        public BooleanNumericConverterNHibernate()
        {
        }

        /// <summary>
        /// Returns whether this converter can convert an object of the given type to the type of this converter (Boolean).
        /// </summary>
        /// <param name="context">Ignored</param>
        /// <param name="sourceType">A <see cref="T:System.Type"/> that represents the type you want to convert from.</param>
        /// <returns>
        ///     <see langword="true "/>if this converter can perform the conversion; otherwise, <see langword="false"/>.
        /// </returns>
        /// <remarks>Accepted types are: Int64, Int32, Int16, Byte, SByte, UInt64, UInt32, UInt16, Decimal</remarks>
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            // any integer type is accepted. No fractional types like float/double.
            switch(sourceType.FullName)
            {
                case "System.Int64":
                case "System.Int32":
                case "System.Int16":
                case "System.Byte":
                case "System.SByte":
                case "System.UInt64":
                case "System.UInt32":
                case "System.UInt16":
                case "System.Decimal":
                    return true;
                default:
                    return false;
            }
        }

        /// <summary>
        /// Returns whether this converter can convert the object to the specified type.
        /// </summary>
        /// <param name="context">Ignored</param>
        /// <param name="destinationType">A <see cref="T:System.Type"/> that represents the type you want to convert to.</param>
        /// <returns>
        ///     <see langword="true "/>if this converter can perform the conversion; otherwise, <see langword="false"/>.
        /// </returns>
        /// <remarks>Accepted types are: Int64, Int32, Int16, Byte, SByte, UInt64, UInt32, UInt16, Decimal. True will be converted to 1, false will be
        /// converted to 0.</remarks>
        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
        {
            // any integer type is accepted. No fractional types like float/double.
            switch(destinationType.FullName)
            {
                case "System.Int64":
                case "System.Int32":
                case "System.Int16":
                case "System.Byte":
                case "System.SByte":
                case "System.UInt64":
                case "System.UInt32":
                case "System.UInt16":
                case "System.Decimal":
                    return true;
                default:
                    return false;
            }
        }

        /// <summary>
        /// Converts the given object to the type of this converter (Boolean).
        /// </summary>
        /// <param name="context">Ignored</param>
        /// <param name="culture">Ignored</param>
        /// <param name="value">The <see cref="T:System.Object"/> to convert.</param>
        /// <returns>
        /// An <see cref="T:System.Object"/> that represents the converted value, which is of type boolean.
        /// </returns>
        /// <exception cref="T:System.NotSupportedException">The conversion could not be performed.</exception>
        public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
        {
            bool toReturn = true;
            switch(value.GetType().FullName)
            {
                case "System.Int64":
                    toReturn = ((long)value > 0);
                    break;
                case "System.Int32":
                    toReturn = ((int)value > 0);
                    break;
                case "System.Int16":
                    toReturn = ((short)value > 0);
                    break;
                case "System.Byte":
                    toReturn = ((byte)value > 0);
                    break;
                case "System.SByte":
                    toReturn = ((SByte)value > 0);
                    break;
                case "System.UInt64":
                    toReturn = ((UInt64)value > 0);
                    break;
                case "System.UInt32":
                    toReturn = ((UInt32)value > 0);
                    break;
                case "System.UInt16":
                    toReturn = ((UInt16)value > 0);
                    break;
                case "System.Decimal":
                    toReturn = ((decimal)value>0);
                    break;
                default:
                    throw new NotSupportedException("Conversion from a value of type '" + value.GetType().ToString() + "' to System.Boolean isn't supported");
            }

            return toReturn;
        }

        /// <summary>
        /// Converts the given value object to the specified type
        /// </summary>
        /// <param name="context">Ignored</param>
        /// <param name="culture">Ignored</param>
        /// <param name="value">The <see cref="T:System.Object"/> to convert.</param>
        /// <param name="destinationType">The <see cref="T:System.Type"/> to convert the <paramref name="value"/> parameter to.</param>
        /// <returns>
        /// An <see cref="T:System.Object"/> that represents the converted value. The value will be 1 if <paramref name="value"/> is true, otherwise 0
        /// </returns>
        /// <exception cref="T:System.ArgumentNullException">The <paramref name="destinationType"/> parameter is <see langword="null"/>.</exception>
        /// <exception cref="T:System.NotSupportedException">The conversion could not be performed.</exception>
        public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
        {
            if(value==null)
            {
                throw new ArgumentNullException("value", "Value can't be null");
            }

            if(! (value is bool))
            {
                throw new ArgumentException("Value isn't of type boolean", "value");
            }

            if((bool)value)
            {
                switch(destinationType.FullName)
                {
                    case "System.Int64":
                        return (long)1;
                    case "System.Int32":
                        return (int)1;
                    case "System.Int16":
                        return (short)1;
                    case "System.Byte":
                        return (byte)1;
                    case "System.SByte":
                        return (SByte)1;
                    case "System.UInt64":
                        return (UInt64)1;
                    case "System.UInt32":
                        return (UInt32)1;
                    case "System.UInt16":
                        return (UInt16)1;
                    case "System.Decimal":
                        return (decimal)1.0;
                    default:
                        throw new NotSupportedException("Conversion to a value of type '" + destinationType.ToString() + "' isn't supported");
                }
            }
            else
            {
                switch(destinationType.FullName)
                {
                    case "System.Int64":
                        return (long)0;
                    case "System.Int32":
                        return (int)0;
                    case "System.Int16":
                        return (short)0;
                    case "System.Byte":
                        return (byte)0;
                    case "System.SByte":
                        return (SByte)0;
                    case "System.UInt64":
                        return (UInt64)0;
                    case "System.UInt32":
                        return (UInt32)0;
                    case "System.UInt16":
                        return (UInt16)0;
                    case "System.Decimal":
                        return (decimal)0;
                    default:
                        throw new NotSupportedException("Conversion to a value of type '" + destinationType.ToString() + "' isn't supported");
                }
            }
        }


        /// <summary>
        /// Creates an instance of the Type that this <see cref="T:System.ComponentModel.TypeConverter"/> is associated with (bool)
        /// </summary>
        /// <param name="context">ignored.</param>
        /// <param name="propertyValues">ignored.</param>
        /// <returns>
        /// An <see cref="T:System.Object"/> of type bool. It always returns 'true' for this converter.
        /// </returns>
        public override object CreateInstance(ITypeDescriptorContext context, System.Collections.IDictionary propertyValues)
        {
            return true;
        }

        #region IUserType Members

        /// <summary>
        /// Reconstruct an object from the cacheable representation. At the very least this
        /// method should perform a deep copy if the type is mutable. (optional operation)
        /// </summary>
        /// <param name="cached">the object to be cached</param>
        /// <param name="owner">the owner of the cached object</param>
        /// <returns>
        /// a reconstructed object from the cachable representation
        /// </returns>
        public object Assemble(object cached, object owner)
        {
            return DeepCopy(cached);
        }

        /// <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>a copy</returns>
        public object DeepCopy(object value)
        {
            return value;
        }

        /// <summary>
        /// Transform the object into its cacheable representation. At the very least this
        /// method should perform a deep copy if the type is mutable. 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>
        /// <returns>a cacheable representation of the object</returns>
        public object Disassemble(object value)
        {
            return DeepCopy(value);
        }

        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <param name="x">The x.</param>
        /// <returns>
        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
        /// </returns>
        public int GetHashCode(object x)
        {
            return x == null? base.GetHashCode() : x.GetHashCode();
        }

        /// <summary>
        /// Are objects of this type mutable?
        /// </summary>
        /// <value></value>
        public bool IsMutable
        {
            get { return true; }
        }

        /// <summary>
        /// Retrieve an instance of the mapped class from a JDBC resultset.
        /// Implementors should handle possibility of null values.
        /// </summary>
        /// <param name="rs">a IDataReader</param>
        /// <param name="names">column names</param>
        /// <param name="owner">the containing entity</param>
        /// <returns></returns>
        /// <exception cref="T:NHibernate.HibernateException">HibernateException</exception>
        public object NullSafeGet(IDataReader rs, string[] names, object owner)
        {
            var rawValue = NHibernateUtil.Int32.NullSafeGet(rs, names[0]);
            if(rawValue==null)
            {
                return null;
            }
            return ConvertFrom(null, null, rawValue);
        }

        /// <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">a IDbCommand</param>
        /// <param name="value">the object to write</param>
        /// <param name="index">command parameter index</param>
        /// <exception cref="T:NHibernate.HibernateException">HibernateException</exception>
        public void NullSafeSet(System.Data.IDbCommand cmd, object value, int index)
        {
            ((IDataParameter)cmd.Parameters[index]).Value = value == null ? DBNull.Value : ConvertTo(null, null, value, typeof(int));
        }

        /// <summary>
        /// During merge, replace the existing (<paramref name="target"/>) value in the entity
        /// we are merging to with a new (<paramref name="original"/>) value from the detached
        /// entity we are merging. For immutable objects, or null values, it is safe to simply
        /// return the first parameter. For mutable objects, it is safe to return a copy of the
        /// first parameter. For objects with component values, it might make sense to
        /// recursively replace component values.
        /// </summary>
        /// <param name="original">the value from the detached entity being merged</param>
        /// <param name="target">the value in the managed entity</param>
        /// <param name="owner">the managed entity</param>
        /// <returns>the value to be merged</returns>
        public object Replace(object original, object target, object owner)
        {
            return original;
        }

        /// <summary>
        /// The type returned by <c>NullSafeGet()</c>
        /// </summary>
        public Type ReturnedType
        {
            get { return typeof(bool); }
        }

        /// <summary>
        /// The SQL types for the columns mapped by this type.
        /// </summary>
        /// <value></value>
        public NHibernate.SqlTypes.SqlType[] SqlTypes
        {
            get
            {
                return new SqlType[] { NHibernateUtil.Int32.SqlType};
            }
        }

        
        /// <summary>
        /// Determines whether the specified <see cref="System.Object"/> is equal to this instance.
        /// </summary>
        /// <param name="x">The <see cref="System.Object"/> to compare with this instance.</param>
        /// <param name="y">The y.</param>
        /// <returns>
        ///     <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
        bool IUserType.Equals(object x, object y)
        {
            if(ReferenceEquals(x, y))
            {
                return true;
            }
            return (x != null && y != null) && x.Equals(y);
        }
        #endregion

    }
}