Implementing a typeconverter

LLBLGen Pro comes with a handy conversion functionality for conversion values at runtime: type converters. Type Converters are objects which can be used to convert an entity field's, typed view field's data or stored procedure parameter data from one .NET type to another .NET type, transparently.

This means that a type converter, once an entity/typed view field/ stored procedure parameter has that type converter set, is a type which simply converts data back and forth. Any .NET type is possible. The LLBLGen Pro Runtime framework supports type converters as described below.

NHibernate also supports type converters, in the form of an implementation of the NHibernate specific IUserType interface. To make your typeconverter class work with NHibernate and the designer, you have to implement a typeconverter class like described below and you have to implement IUserType on that class as well, where the TypeConverter specifics are for the designer and IUserType are for usage at runtime.

Starting with v3, you don't have to create a type converter for Enum types anymore.

The description implementation is for an LLBLGen Pro runtime framework type converter. To use this guide for NHibernate, follow the steps below and additionally implement IUserType on the type converter class. Be sure that NHibernate.dll is reachable from your plugins folder to let the designer be able to create a type instance of your type converter.

At the end of this section, two examples are given of the same type converter, a boolean <-> numeric value converter. One implementation for LLBLGen Pro and one implementation for NHibernate. We added a lot of XML comments to illustrate the usage of the methods. The actual code of the type converter is quite small, the overall size is increased by said XML comments.

Writing your own type converter, a guide.

This guide steps through the steps to get you up and running with your first type converter for your LLBLGen Pro project. First, familiar yourself with the details about the type converter system, which are described in the LLBLGen Pro Concepts section. Then go through the various steps to create your own type converter.

Be sure to think through from which type to which type you want to convert and what you possibly need to get the conversions done. You have to implement both the conversion from the native .NET type of a field to the core .NET type of the type converter, and back.

Step 0: familiar yourself with the type converter system details

All type converters have to be implemented using .NET and have to derive from the .NET System.ComponentModel.TypeConverter baseclass. This base class implements the foundation for a type converter and because it's part of .NET, it's available in the designer and also at runtime in the ORMSupportClasses and generated code.

A type converter is developed in a class library assembly, which results in a .dll. Each type converter dll has to be placed in the TypeConverters folder of the LLBLGen Pro installation, or in the Additional Typeconverters folder specified in the project properties of the project you want to use the type converter with before the LLBLGen Pro designer is started.

All dlls are loaded into the designer and used if one of the components in the dlls derives from System.ComponentModel.TypeConverter. If a type converter crashes during startup, it is mentioned and the type converter is ignored. The user has to restart llblgen pro if a new version of a type converter has to be loaded.

Step 1: setting up VS.NET

Create a library project in your language of choice, you don't need any references to LLBLGen Pro assemblies. Add a class which derives from System.ComponentModel.TypeConverter. This will be your TypeConverter class. You should add namespace references to your code file to at least System and System.ComponentModel.

Create five overrides in your type converter class for the following methods

  • CanConvertFrom(ITypeDescriptorContext context, Type sourceType). In this method override you check if sourceType can be converted to the core .NET type of your type converter and you return true or false based on that check.
  • CanConvertTo(ITypeDescriptorContext context, Type destinationType). In this method override you check if the core .NET type can be converted to the destinationType type and you return true or false based on that check.
  • ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value). In this method override you convert the value in the parameter value to a value of the core type of the type converter and you return that conversion result. Don't convert DBNull.Value to null. See note below.
  • ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType) In this method override you convert the passed in value in the parameter value to the type specified in destinationType and you return that conversion result.
  • CreateInstance(ITypeDescriptorContext context, System.Collections.IDictionary propertyValues). This method override should return a new instance of the core type of the type converter. This method is used by LLBLGen Pro to determine what the code .NET type is of the type converter, as the System.ComponentModel.TypeConverter doesn't have a method for that.

Add a Description attribute to your class, which describes your type converter's purpose.

Done!

Important!

In the ConvertFrom method of your type converter, it's important to return the passed in value as-is if the value represents a NULL value (null or DBNull.Value). This means you shouldn't return 'null' if the value is DBNull.Value as the framework does this conversion as well and by changing DBNull.Value into null the highly optimized projection code will likely miss the null value (as it expects DBNull.Value as it assumes it works directly on the raw row from the DataReader) and return the wrong value instead of properly returning null.

This is an example of what you should do (this converts Double to Decimal, supporting NULL values):

public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
    if (value == null || value == DBNull.Value)
    {
        // return value here, as DBNull.Value conversions happen elsewhere.
        return value;
    }

    if (!(value is double))
    {
        throw new ArgumentException("Value is not a double");
    }
    return Convert.ToDecimal(value);
}

Step 2: compiling and debugging

After you've done the previous steps, you have code which should work as a type converter. Compile the assembly and place it in the TypeConverters folder of the LLBLGen Pro installation or in the Additional Typeconverters folder specified in the project properties of the project you want to use the type converter with and start LLBLGen Pro and check if everything works as planned.

You can do that by loading a project and opening an entity which has fields of a .NET type which can be converted by the type converter and see if you can select the type converter from the drop down list when selecting the field in the fields grid.

Now, it can happen that there is a bug in your type converter. If you want to debug a type converter in the designer, you can only debug the CanConvertTo/From and CreateInstance methods as these are the ones called by LLBLGen Pro. To do so, follow these steps:

  • Load the type converter project in VS.NET
  • Create a debug build and copy the .dll and the .pdb file into the TypeConverters folder
  • Run the LLBLGen Pro designer
  • In VS.NET, press cntrl-alt-p. This allows you to attach to a process, select LLBLGenPro.exe and just select the managed debugger.
  • Set a breakpoint in the method you want to break and switch to LLBLGen Pro and be sure LLBLGen Pro should select your type converter. You should now break at the breakpoint you've set.

To debug the ConvertFrom/To methods at runtime in your own application, just build the generated code with a debug build of the type converter vs.net project, be sure the .pdb file of the type converter project is placed in your application's run folder and follow the same procedure: attach to your application from within VS.NET, and set a breakpoint in ConvertTo/From and execute code which should convert data from / to using the type converter.

Example

Below the same type converter is implemented twice: once for the LLBLGen Pro runtime framework and once for NHibernate. The type converter is the one shipped with LLBLGen Pro and converts between a boolean value (model side) and a numeric value (db side). The code is given in C# and should be placed inside a separate .NET 3.5 or higher compiled assembly / vs.net project, so you can use the same assembly in the designer and in your own code using the target framework of choice (LLBLGen Pro runtime framework or NHibernate)

LLBLGen Pro runtime framework version

//////////////////////////////////////////////////////////////////////
// Public Class definitions for the Type converter assembly of LLBLGen Pro.
// (c) 2002-2010 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-2010 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;

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 a numeric database field")]
    public class BooleanNumericConverter : TypeConverter
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="BooleanNumericConverter"/> class.
        /// </summary>
        public BooleanNumericConverter()
        {
        }

        /// <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;
        }
    }
}

NHibernate version

The NHibernate version implements IUserType besides being a TypeConverter derived class. IUserType is an interface exposed by NHibernate, so you need to reference NHibernate.dll in your vs.net project which contains the class specified below. This also means that NHibernate.dll has to be present in the TypeConverters folder you place the compiled assembly in.

//////////////////////////////////////////////////////////////////////
// Public Class definitions for the Type converter assembly of LLBLGen Pro.
// (c) 2002-2010 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-2010 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

    }
}