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.0, you don't have to create a type converter for Enum types anymore.
It's important that your TypeConverter classes are compiled against .NET 3.5, as the designer runs on .NET 3.5. If you run the designer on .NET 4.0, be sure to compile your type converter against .NET 4.0 as well. If you compile your type converter against .NET 4.0, it's not usable in the designer running on .NET 3.5.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.
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: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)
////////////////////////////////////////////////////////////////////// // 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; } } }
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 } }