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