/// <summary>
        /// Look up a DbType from a .Net type.
        /// </summary>
        /// <param name="mapping">The column mapping to analyze.</param>
        /// <param name="parameterType">The expected sql parameter type. Used as the default.</param>
        /// <returns>The equivalend DbType.</returns>
        private static DbType LookupDbType(ColumnMappingEventArgs mapping, DbType parameterType)
        {
            var    type = mapping.ClassPropInfo.MemberType;
            DbType sqlType;

            // if the type is nullable, get the underlying type
            var nullUnderlyingType = Nullable.GetUnderlyingType(type);

            if (nullUnderlyingType != null)
            {
                type = nullUnderlyingType;
            }

            // if it's an enum, get the underlying type
            if (type.IsEnum)
            {
                type = Enum.GetUnderlyingType(type);
            }

            // look up the type
            if (_typeToDbTypeMap.TryGetValue(type, out sqlType))
            {
                return(sqlType);
            }

            // special cases for XmlDocument and XDocument
            if (type == typeof(XmlDocument))
            {
                return(DbType.Xml);
            }
            if (type == typeof(XDocument))
            {
                return(DbType.Xml);
            }

            // support for enumerables
            if (typeof(IEnumerable).IsAssignableFrom(type))
            {
                // if the list should be serialized, then return a string
                if (mapping.SerializationMode != SerializationMode.Default)
                {
                    return(DbType.String);
                }

                // use -1 to denote its a list, hacky but will work on any DB
                return(DbTypeEnumerable);
            }

            // sql udts are udts
            if (TypeHelper.IsSqlUserDefinedType(type))
            {
                return(DbType.Object);
            }

            // let's see if the type can be directly converted to the parameter type
            return(parameterType);
        }
        /// <summary>
        /// Emit the IL to convert the current value on the stack and set the value of the object.
        /// </summary>
        /// <param name="il">The IL generator to output to.</param>
        /// <param name="sourceType">The current type of the value.</param>
        /// <param name="mapping">The column mapping to use.</param>
        /// <remarks>
        ///	Expects the stack to contain:
        ///		Target Object
        ///		Value to set
        /// The value is first converted to the type required by the method parameter, then sets the property.
        /// </remarks>
        /// <returns>A label that needs to be marked at the end of a succesful set.</returns>
        public static Label EmitConvertAndSetValue(ILGenerator il, Type sourceType, ColumnMappingEventArgs mapping)
        {
            var method = mapping.ClassPropInfo;

            // targetType - the target type we need to convert to
            // underlyingTargetType - if the target type is nullable, we need to look at the underlying target type
            // rawTargetType - if the underlying target type is enum, we need to look at the underlying target type for that
            // sourceType - this is the type of the data in the data set
            Type targetType           = method.MemberType;
            Type underlyingTargetType = Nullable.GetUnderlyingType(targetType) ?? targetType;

            // some labels that we need
            Label isDbNullLabel = il.DefineLabel();
            Label finishLabel   = il.DefineLabel();

            // if the value is DbNull, then we continue to the next item
            il.Emit(OpCodes.Dup);                                                       // dup value, stack => [target][value][value]
            il.Emit(OpCodes.Isinst, typeof(DBNull));                                    // isinst DBNull:value, stack => [target][value-as-object][DBNull or null]
            il.Emit(OpCodes.Brtrue_S, isDbNullLabel);                                   // br.true isDBNull, stack => [target][value-as-object]

            // handle the special target types first
            if (targetType == typeof(char))
            {
                // char
                il.EmitCall(OpCodes.Call, _readChar, null);
            }
            else if (targetType == typeof(char?))
            {
                // char?
                il.EmitCall(OpCodes.Call, _readNullableChar, null);
            }
            else if (targetType == TypeHelper.LinqBinaryType)
            {
                // unbox sql byte arrays to Linq.Binary

                // before: stack => [target][object-value]
                // after: stack => [target][byte-array-value]
                il.Emit(OpCodes.Unbox_Any, typeof(byte[]));                 // stack is now [target][byte-array]
                // before: stack => [target][byte-array-value]
                // after: stack => [target][Linq.Binary-value]
                il.Emit(OpCodes.Newobj, TypeHelper.LinqBinaryCtor);
            }
            else if (targetType == typeof(XmlDocument))
            {
                // special handler for XmlDocuments

                // before: stack => [target][object-value]
                il.Emit(OpCodes.Call, _readXmlDocument);

                // after: stack => [target][xmlDocument]
            }
            else if (targetType == typeof(XDocument))
            {
                // special handler for XDocuments

                // before: stack => [target][object-value]
                il.Emit(OpCodes.Call, _readXDocument);

                // after: stack => [target][xDocument]
            }
            else if (sourceType == typeof(string) && CanDeserialize(mapping.Serializer, targetType))
            {
                // we are getting a string from the database, but the target is not a string, and it's a reference type
                // assume the column is a serialized data type and that we want to deserialize it

                // before: stack => [target][object-value]
                il.Emit(OpCodes.Castclass, typeof(string));
                il.EmitLoadType(targetType);

                // after: stack => [target][object-value][memberType]

                // determine the serializer to use to convert the string to an object
                var serializerMethod = mapping.Serializer.GetMethod("Deserialize", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string), typeof(Type) }, null);
                if (serializerMethod == null)
                {
                    throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Serializer type {0} needs the method 'public static object Deserialize(string, Type)'", mapping.Serializer.Name));
                }
                il.Emit(OpCodes.Call, serializerMethod);
                il.Emit(OpCodes.Unbox_Any, targetType);
            }
            else if (underlyingTargetType.IsEnum && sourceType == typeof(string))
            {
                var localString = il.DeclareLocal(typeof(string));

                // if we are converting a string to an enum, then parse it.
                // see if the value from the database is a string. if so, we need to parse it. If not, we will just try to unbox it.
                il.Emit(OpCodes.Isinst, typeof(string));                                        // is string, stack => [target][string]
                il.Emit(OpCodes.Stloc, localString);                                            // pop loc.2 (enum), stack => [target]

                // call enum.parse (type, value, true)
                il.EmitLoadType(underlyingTargetType);
                il.Emit(OpCodes.Ldloc, localString);                                    // push enum, stack => [target][enum-type][string]
                il.Emit(OpCodes.Ldc_I4_1);                                              // push true, stack => [target][enum-type][string][true]
                il.EmitCall(OpCodes.Call, _enumParse, null);                            // call Enum.Parse, stack => [target][enum-as-object]

                // Enum.Parse returns an object, which we need to unbox to the enum value
                il.Emit(OpCodes.Unbox_Any, underlyingTargetType);
            }
            else if (EmitConstructorConversion(il, sourceType, targetType))
            {
                // target type can be constructed from source type
            }
            else
            {
                // this isn't a system value type, so unbox to the type the reader is giving us (this is a system type, hopefully)
                // now we have an unboxed sourceType
                il.Emit(OpCodes.Unbox_Any, sourceType);

                if (sourceType != targetType)
                {
                    // attempt to convert the value to the target type
                    if (!EmitConversionOrCoersion(il, sourceType, targetType))
                    {
                        if (sourceType != targetType)
                        {
                            throw new InvalidOperationException(String.Format(
                                                                    CultureInfo.InvariantCulture,
                                                                    "Field {0} cannot be converted from {1} to {2}. Create a conversion constructor or conversion operator.",
                                                                    method.Name,
                                                                    sourceType.AssemblyQualifiedName,
                                                                    targetType.AssemblyQualifiedName));
                        }
                    }

                    // if the target is nullable, then construct the nullable from the data
                    if (Nullable.GetUnderlyingType(targetType) != null)
                    {
                        il.Emit(OpCodes.Newobj, targetType.GetConstructor(new[] { underlyingTargetType }));
                    }
                }
            }

            /////////////////////////////////////////////////////////////////////
            // now the stack has [target][value-unboxed]. we can set the value now
            method.EmitSetValue(il);

            // stack is now EMPTY
            /////////////////////////////////////////////////////////////////////

            /////////////////////////////////////////////////////////////////////
            // jump over our DBNull handler
            il.Emit(OpCodes.Br_S, finishLabel);
            /////////////////////////////////////////////////////////////////////

            /////////////////////////////////////////////////////////////////////
            // cleanup after IsDBNull.
            /////////////////////////////////////////////////////////////////////
            il.MarkLabel(isDbNullLabel);                                                                // stack => [target][value]
            il.Emit(OpCodes.Pop);                                                                       // pop value, stack => [target]

            // if the type is an object, set the value to null
            // this is necessary for overwriting output parameters,
            // as well as overwriting any properties that may be set in the constructor of the object
            if (!method.MemberType.IsValueType)
            {
                il.Emit(OpCodes.Ldnull);                                                                        // push null
                method.EmitSetValue(il);
            }
            else
            {
                // we didn't call setvalue, so pop the target object off the stack
                il.Emit(OpCodes.Pop);                                                                           // pop target, stack => [empty]
            }

            return(finishLabel);
        }
		/// <summary>
		/// Emit the IL to convert the current value on the stack and set the value of the object.
		/// </summary>
		/// <param name="il">The IL generator to output to.</param>
		/// <param name="sourceType">The current type of the value.</param>
		/// <param name="mapping">The column mapping to use.</param>
		/// <remarks>
		///	Expects the stack to contain:
		///		Target Object
		///		Value to set
		/// The value is first converted to the type required by the method parameter, then sets the property.
		/// </remarks>
		/// <returns>A label that needs to be marked at the end of a succesful set.</returns>
		public static Label EmitConvertAndSetValue(ILGenerator il, Type sourceType, ColumnMappingEventArgs mapping)
		{
			var method = mapping.ClassPropInfo;

			// targetType - the target type we need to convert to
			// underlyingTargetType - if the target type is nullable, we need to look at the underlying target type
			// rawTargetType - if the underlying target type is enum, we need to look at the underlying target type for that
			// sourceType - this is the type of the data in the data set
			Type targetType = method.MemberType;
			Type underlyingTargetType = Nullable.GetUnderlyingType(targetType) ?? targetType;

			// some labels that we need
			Label isDbNullLabel = il.DefineLabel();
			Label finishLabel = il.DefineLabel();

			// if the value is DbNull, then we continue to the next item
			il.Emit(OpCodes.Dup);								// dup value, stack => [target][value][value]
			il.Emit(OpCodes.Isinst, typeof(DBNull));			// isinst DBNull:value, stack => [target][value-as-object][DBNull or null]
			il.Emit(OpCodes.Brtrue_S, isDbNullLabel);			// br.true isDBNull, stack => [target][value-as-object]

			// handle the special target types first
			if (targetType == typeof(char))
			{
				// char
				il.EmitCall(OpCodes.Call, _readChar, null);
			}
			else if (targetType == typeof(char?))
			{
				// char?
				il.EmitCall(OpCodes.Call, _readNullableChar, null);
			}
			else if (targetType == TypeHelper.LinqBinaryType)
			{
				// unbox sql byte arrays to Linq.Binary

				// before: stack => [target][object-value]
				// after: stack => [target][byte-array-value]
				il.Emit(OpCodes.Unbox_Any, typeof(byte[])); // stack is now [target][byte-array]
				// before: stack => [target][byte-array-value]
				// after: stack => [target][Linq.Binary-value]
				il.Emit(OpCodes.Newobj, TypeHelper.LinqBinaryCtor);
			}
			else if (targetType == typeof(XmlDocument))
			{
				// special handler for XmlDocuments

				// before: stack => [target][object-value]
				il.Emit(OpCodes.Call, _readXmlDocument);

				// after: stack => [target][xmlDocument]
			}
			else if (targetType == typeof(XDocument))
			{
				// special handler for XDocuments

				// before: stack => [target][object-value]
				il.Emit(OpCodes.Call, _readXDocument);

				// after: stack => [target][xDocument]
			}
			else if (sourceType == typeof(string) && CanDeserialize(mapping.Serializer, targetType))
			{
				// we are getting a string from the database, but the target is not a string, and it's a reference type
				// assume the column is a serialized data type and that we want to deserialize it

				// before: stack => [target][object-value]
				il.Emit(OpCodes.Castclass, typeof(string));
				il.EmitLoadType(targetType);

				// after: stack => [target][object-value][memberType]

				// determine the serializer to use to convert the string to an object
				var serializerMethod = mapping.Serializer.GetMethod("Deserialize", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(string), typeof(Type) }, null);
				if (serializerMethod == null)
					throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Serializer type {0} needs the method 'public static object Deserialize(string, Type)'", mapping.Serializer.Name));
				il.Emit(OpCodes.Call, serializerMethod);
				il.Emit(OpCodes.Unbox_Any, targetType);
			}
			else if (underlyingTargetType.IsEnum && sourceType == typeof(string))
			{
				var localString = il.DeclareLocal(typeof(string));

				// if we are converting a string to an enum, then parse it.
				// see if the value from the database is a string. if so, we need to parse it. If not, we will just try to unbox it.
				il.Emit(OpCodes.Isinst, typeof(string));			// is string, stack => [target][string]
				il.Emit(OpCodes.Stloc, localString);				// pop loc.2 (enum), stack => [target]

				// call enum.parse (type, value, true)
				il.EmitLoadType(underlyingTargetType);
				il.Emit(OpCodes.Ldloc, localString);				// push enum, stack => [target][enum-type][string]
				il.Emit(OpCodes.Ldc_I4_1);							// push true, stack => [target][enum-type][string][true]
				il.EmitCall(OpCodes.Call, _enumParse, null);		// call Enum.Parse, stack => [target][enum-as-object]

				// Enum.Parse returns an object, which we need to unbox to the enum value
				il.Emit(OpCodes.Unbox_Any, underlyingTargetType);
			}
			else if (EmitConstructorConversion(il, sourceType, targetType))
			{
				// target type can be constructed from source type
			}
			else
			{
				// this isn't a system value type, so unbox to the type the reader is giving us (this is a system type, hopefully)
				// now we have an unboxed sourceType
				il.Emit(OpCodes.Unbox_Any, sourceType);

				if (sourceType != targetType)
				{
					// attempt to convert the value to the target type
					if (!EmitConversionOrCoersion(il, sourceType, targetType))
					{
						if (sourceType != targetType)
						{
							throw new InvalidOperationException(String.Format(
								CultureInfo.InvariantCulture,
								"Field {0} cannot be converted from {1} to {2}. Create a conversion constructor or conversion operator.",
								method.Name,
								sourceType.AssemblyQualifiedName,
								targetType.AssemblyQualifiedName));
						}
					}

					// if the target is nullable, then construct the nullable from the data
					if (Nullable.GetUnderlyingType(targetType) != null)
						il.Emit(OpCodes.Newobj, targetType.GetConstructor(new[] { underlyingTargetType }));
				}
			}

			/////////////////////////////////////////////////////////////////////
			// now the stack has [target][value-unboxed]. we can set the value now
			method.EmitSetValue(il);

			// stack is now EMPTY
			/////////////////////////////////////////////////////////////////////

			/////////////////////////////////////////////////////////////////////
			// jump over our DBNull handler
			il.Emit(OpCodes.Br_S, finishLabel);
			/////////////////////////////////////////////////////////////////////

			/////////////////////////////////////////////////////////////////////
			// cleanup after IsDBNull.
			/////////////////////////////////////////////////////////////////////
			il.MarkLabel(isDbNullLabel);							// stack => [target][value]
			il.Emit(OpCodes.Pop);									// pop value, stack => [target]

			// if the type is an object, set the value to null
			// this is necessary for overwriting output parameters,
			// as well as overwriting any properties that may be set in the constructor of the object
			if (!method.MemberType.IsValueType)
			{
				il.Emit(OpCodes.Ldnull);							// push null
				method.EmitSetValue(il);
			}
			else
			{
				// we didn't call setvalue, so pop the target object off the stack
				il.Emit(OpCodes.Pop);								// pop target, stack => [empty]
			}

			return finishLabel;
		}
		/// <summary>
		/// Look up a DbType from a .Net type.
		/// </summary>
		/// <param name="mapping">The column mapping to analyze.</param>
		/// <param name="parameterType">The expected sql parameter type. Used as the default.</param>
		/// <returns>The equivalend DbType.</returns>
		private static DbType LookupDbType(ColumnMappingEventArgs mapping, DbType parameterType)
		{
			var type = mapping.ClassPropInfo.MemberType;
			DbType sqlType;

			// if the type is nullable, get the underlying type
			var nullUnderlyingType = Nullable.GetUnderlyingType(type);
			if (nullUnderlyingType != null)
				type = nullUnderlyingType;

			// if it's an enum, get the underlying type
			if (type.IsEnum)
				type = Enum.GetUnderlyingType(type);

			// look up the type
			if (_typeToDbTypeMap.TryGetValue(type, out sqlType))
				return sqlType;

			// special cases for XmlDocument and XDocument
			if (type == typeof(XmlDocument))
				return DbType.Xml;
			if (type == typeof(XDocument))
				return DbType.Xml;

			// support for enumerables
			if (typeof(IEnumerable).IsAssignableFrom(type))
			{
				// if the list should be serialized, then return a string
				if (mapping.SerializationMode != SerializationMode.Default)
					return DbType.String;

				// use -1 to denote its a list, hacky but will work on any DB
				return DbTypeEnumerable;
			}

			// sql udts are udts
			if (TypeHelper.IsSqlUserDefinedType(type))
				return DbType.Object;

			// let's see if the type can be directly converted to the parameter type
			return parameterType;
		}