/// <summary> /// Emits a conversion using Convert.ChangeType if the source type supports IConvertible. /// </summary> /// <param name="il">The ILGenerator to use.</param> /// <param name="sourceType">The source type of the data.</param> /// <param name="targetType">The target type of the data.</param> /// <returns>True if a conversion was emitted, false otherwise.</returns> private static bool EmitIConvertibleConversion(ILGenerator il, Type sourceType, Type targetType) { if (!sourceType.GetInterfaces().Contains(typeof(IConvertible))) { return(false); } if (sourceType.GetTypeInfo().IsValueType) { il.Emit(OpCodes.Box, sourceType); } IlHelper.EmitLoadType(il, targetType); il.Emit(OpCodes.Call, typeof(Convert).GetMethod("ChangeType", new Type[] { typeof(object), typeof(Type) })); il.Emit(OpCodes.Unbox_Any, targetType); return(true); }
/// <summary> /// Create a deserializer that just reads a value. /// </summary> /// <param name="type">The type to read.</param> /// <param name="column">The index of column to read.</param> /// <returns>A method that reads the value.</returns> private static DynamicMethod CreateValueDeserializer(Type type, int column) { var dm = new DynamicMethod( String.Format(CultureInfo.InvariantCulture, "Deserialize-{0}-{1}", type.FullName, Guid.NewGuid()), type, new[] { typeof(IDataReader) }, true); // get the il generator and put some local variables on the stack var il = dm.GetILGenerator(); // need to call IDataReader.GetItem to get the value of the field il.Emit(OpCodes.Ldarg_0); // push arg.0 (reader), stack => [target][reader] IlHelper.EmitLdInt32(il, column); // push index, stack => [target][reader][index] il.Emit(OpCodes.Callvirt, _iDataReaderGetItem); // call getItem, stack => [target][value-as-object] il.Emit(OpCodes.Call, typeof(ClassDeserializerGenerator).GetMethod("DBValueToT", BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod(type)); il.Emit(OpCodes.Ret); return(dm); }
private static void EmitMethodImpl(ModuleBuilder mb, TypeBuilder tb, MethodInfo interfaceMethod, FieldBuilder connectionField) { // look at the parameters on the interface var parameters = interfaceMethod.GetParameters(); var parameterTypes = parameters.Select(p => p.ParameterType).ToArray(); // determine the proper method to call MethodInfo executeMethod = GetExecuteMethod(interfaceMethod); // get the sql attributes from the method and class/interface var sqlAttribute = interfaceMethod.GetCustomAttributes(false).OfType <SqlAttribute>().FirstOrDefault() ?? new SqlAttribute(); var typeSqlAttribute = interfaceMethod.DeclaringType.GetCustomAttributes(false).OfType <SqlAttribute>().FirstOrDefault() ?? new SqlAttribute(); // calculate the query parameters var schema = sqlAttribute.Schema ?? typeSqlAttribute.Schema; var procName = (executeMethod.DeclaringType == typeof(DBConnectionExtensions)) ? Regex.Replace(interfaceMethod.Name, "Async$", String.Empty, RegexOptions.IgnoreCase) : interfaceMethod.Name; var sql = sqlAttribute.Sql ?? typeSqlAttribute.Sql ?? procName; var commandType = sqlAttribute.CommandType ?? typeSqlAttribute.CommandType ?? (sql.Contains(' ') ? CommandType.Text : CommandType.StoredProcedure); if (commandType == CommandType.StoredProcedure && !schema.IsNullOrWhiteSpace() && !sql.Contains('.')) { sql = schema.Trim() + "." + sql; } // start a new method MethodBuilder m = tb.DefineMethod(interfaceMethod.Name, MethodAttributes.Public | MethodAttributes.Virtual); TypeHelper.CopyMethodSignature(interfaceMethod, m); ILGenerator mIL = m.GetILGenerator(); LocalBuilder parameterWrapper = null; var executeParameters = executeMethod.GetParameters(); for (int i = 0; i < executeParameters.Length; i++) { switch (executeParameters[i].Name) { case "connection": // get the connection from the getConnection method mIL.Emit(OpCodes.Ldarg_0); mIL.Emit(OpCodes.Ldfld, connectionField); if (connectionField.FieldType == typeof(Func <IDbConnection>)) { mIL.Emit(OpCodes.Call, connectionField.FieldType.GetMethod("Invoke")); } break; case "sql": // if the sql attribute is on the method, use that mIL.Emit(OpCodes.Ldstr, sql); break; case "parameters": // load all of the parameters and convert it to a parameters object if (parameters.Length == 0) { // no parameters, just pass null mIL.Emit(OpCodes.Ldnull); } else if (parameters.Length == 1 && !TypeHelper.IsAtomicType(parameters[0].ParameterType)) { // one parameter that is a non-atomic object, just pass it and let the insight framework handle it mIL.Emit(OpCodes.Ldarg_1); if (parameters[0].ParameterType.IsValueType) { mIL.Emit(OpCodes.Box, parameters[0].ParameterType); } } else { // create a class for the parameters and stick them in there Type parameterWrapperType = CreateParameterClass(mb, parameters); for (int pi = 0; pi < parameters.Length; pi++) { mIL.Emit(OpCodes.Ldarg, pi + 1); } mIL.Emit(OpCodes.Newobj, parameterWrapperType.GetConstructors()[0]); // store the parameters in a local so we can unwrap output parameters parameterWrapper = mIL.DeclareLocal(parameterWrapperType); mIL.Emit(OpCodes.Dup); mIL.Emit(OpCodes.Stloc, parameterWrapper); } break; case "outputParameters": // fill in the output parameters object with the temporary parameters object if (parameterWrapper != null) { mIL.Emit(OpCodes.Ldloc, parameterWrapper.LocalIndex); } else { mIL.Emit(OpCodes.Ldnull); } break; case "inserted": // always pass argument 1 in mIL.Emit(OpCodes.Ldarg_1); break; case "returns": if (!EmitSpecialParameter(mIL, "returns", parameters, executeParameters)) { GenerateReturnsStructure(interfaceMethod, mb, mIL); } break; case "commandType": if (EmitSpecialParameter(mIL, "commandType", parameters, executeParameters)) { break; } IlHelper.EmitLdInt32(mIL, (int)commandType); break; case "commandBehavior": if (EmitSpecialParameter(mIL, "commandBehavior", parameters, executeParameters)) { break; } IlHelper.EmitLdInt32(mIL, (int)CommandBehavior.Default); break; case "closeConnection": if (EmitSpecialParameter(mIL, "closeConnection", parameters, executeParameters)) { break; } IlHelper.EmitLdInt32(mIL, (int)0); break; case "commandTimeout": if (EmitSpecialParameter(mIL, "commandTimeout", parameters, executeParameters)) { break; } var commandTimeout = mIL.DeclareLocal(typeof(int?)); mIL.Emit(OpCodes.Ldloca_S, commandTimeout); mIL.Emit(OpCodes.Initobj, typeof(int?)); mIL.Emit(OpCodes.Ldloc, commandTimeout); break; case "transaction": if (EmitSpecialParameter(mIL, "transaction", parameters, executeParameters)) { break; } mIL.Emit(OpCodes.Ldnull); break; case "cancellationToken": if (EmitSpecialParameter(mIL, "cancellationToken", parameters, executeParameters)) { break; } var cancellationToken = mIL.DeclareLocal(typeof(CancellationToken?)); mIL.Emit(OpCodes.Ldloca_S, cancellationToken); mIL.Emit(OpCodes.Initobj, typeof(CancellationToken?)); mIL.Emit(OpCodes.Ldloc, cancellationToken); break; default: throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Cannot determine how to generate parameter {1} for method {0}", executeMethod.Name, executeParameters[i].Name)); } } // call the execute method mIL.Emit(OpCodes.Call, executeMethod); // if the method returns void, throw away the return value if (interfaceMethod.ReturnType == typeof(void)) { mIL.Emit(OpCodes.Pop); } // copy the output parameters from our parameter structure back to the output parameters EmitOutputParameters(parameters, parameterWrapper, mIL); mIL.Emit(OpCodes.Ret); }
static Action <IDbCommand, object> CreateClassInputParameterGenerator(IDbCommand command, Type type) { var provider = InsightDbProvider.For(command); var parameters = provider.DeriveParameters(command); // special case if the parameters object is an IEnumerable or Array // look for the parameter that is a Structured object and pass the array to the TVP // note that string supports ienumerable, so exclude atomic types var enumerable = type.GetInterfaces().FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable <>)); if (enumerable != null && type != typeof(string) && parameters.OfType <IDataParameter>().Where(p => p.Direction.HasFlag(ParameterDirection.Input)).Count() == 1) { return((IDbCommand cmd, object o) => { // don't use the provider above. The command may be unwrapped by the time we get back here var tableParameter = InsightDbProvider.For(cmd).CloneParameter(cmd, parameters.OfType <IDataParameter>().Single(p => p.Direction.HasFlag(ParameterDirection.Input))); cmd.Parameters.Add(tableParameter); ListParameterHelper.AddListParameter(tableParameter, o, cmd); }); } // get the mapping of the properties for the type var mappings = ColumnMapping.Parameters.CreateMapping(type, null, command, parameters, null, 0, parameters.Count, true); // start creating a dynamic method Type typeOwner = type.HasElementType ? type.GetElementType() : type; var dm = new DynamicMethod(String.Format(CultureInfo.InvariantCulture, "CreateInputParameters-{0}", Guid.NewGuid()), null, new[] { typeof(IDbCommand), typeof(object) }, typeOwner, true); var il = dm.GetILGenerator(); // copy the parameters into the command object var parametersLocal = il.DeclareLocal(typeof(IDataParameter[])); new StaticFieldStorage(provider).EmitLoad(il); il.Emit(OpCodes.Ldarg_0); new StaticFieldStorage(parameters).EmitLoad(il); il.Emit(OpCodes.Call, typeof(InsightDbProvider).GetMethod("CopyParameters", BindingFlags.NonPublic | BindingFlags.Instance)); il.Emit(OpCodes.Stloc, parametersLocal); // go through all of the mappings for (int i = 0; i < mappings.Length; i++) { var mapping = mappings[i]; var dbParameter = parameters[i]; // if there is no mapping for the parameter if (mapping == null) { // sql will silently eat table parameters that are not specified, and that can be difficult to debug if (provider.IsTableValuedParameter(command, dbParameter)) { throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Table parameter {0} must be specified", dbParameter.ParameterName)); } // unspecified input parameters get skipped if (dbParameter.Direction == ParameterDirection.Input) { parameters[i] = null; } continue; } // get the parameter il.Emit(OpCodes.Ldloc, parametersLocal); il.Emit(OpCodes.Ldc_I4, i); il.Emit(OpCodes.Ldelem, typeof(IDataParameter)); var prop = mapping.ClassPropInfo; // look up the best type to use for the parameter DbType sqlType = LookupDbType(mapping, dbParameter.DbType); // give the provider an opportunity to fix up the template parameter (e.g. set UDT type names) provider.FixupParameter(command, dbParameter, sqlType, prop.MemberType); /////////////////////////////////////////////////////////////// // We have a parameter, start handling all of the other types /////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////// // Get the value from the object onto the stack /////////////////////////////////////////////////////////////// il.Emit(OpCodes.Ldarg_1); prop.EmitGetValue(il); /////////////////////////////////////////////////////////////// // Special case support for enumerables. If the type is -1 (our workaround, then call the list parameter method) /////////////////////////////////////////////////////////////// if (sqlType == DbTypeEnumerable) { // we have the parameter and the value as object, add the command il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Call, typeof(ListParameterHelper).GetMethod("AddListParameter", BindingFlags.Static | BindingFlags.NonPublic)); continue; } // if this is a value type, then box the value so the compiler can check the type and we can call methods on it if (prop.MemberType.IsValueType) { il.Emit(OpCodes.Box, prop.MemberType); } // special conversions for timespan to datetime if ((sqlType == DbType.Time && dbParameter.DbType != DbType.Time) || (dbParameter.DbType == DbType.DateTime || dbParameter.DbType == DbType.DateTime2 || dbParameter.DbType == DbType.DateTimeOffset)) { IlHelper.EmitLdInt32(il, (int)dbParameter.DbType); il.Emit(OpCodes.Call, typeof(TypeConverterGenerator).GetMethod("ObjectToSqlDateTime")); } // if it's class type, boxed value type (in an object), or nullable, then we have to check for null Label readyToSetLabel = il.DefineLabel(); if (!prop.MemberType.IsValueType || Nullable.GetUnderlyingType(prop.MemberType) != null) { Label notNull = il.DefineLabel(); // check to see if it's not null il.Emit(OpCodes.Dup); il.Emit(OpCodes.Brtrue_S, notNull); // it's null. replace the value with DbNull il.Emit(OpCodes.Pop); il.Emit(OpCodes.Ldsfld, _dbNullValue); // value is set to null. ready to set the property. il.Emit(OpCodes.Br_S, readyToSetLabel); // we know the value is not null il.MarkLabel(notNull); } /////////////////////////////////////////////////////////////// // if this is a linq binary, convert it to a byte array /////////////////////////////////////////////////////////////// if (prop.MemberType == TypeHelper.LinqBinaryType) { il.Emit(OpCodes.Callvirt, TypeHelper.LinqBinaryToArray); } else if (prop.MemberType == typeof(XmlDocument)) { // we are sending up an XmlDocument. ToString just returns the classname, so use the outerxml. il.Emit(OpCodes.Callvirt, prop.MemberType.GetProperty("OuterXml").GetGetMethod()); } else if (prop.MemberType == typeof(XDocument)) { // we are sending up an XDocument. Use ToString. il.Emit(OpCodes.Callvirt, prop.MemberType.GetMethod("ToString", new Type[] { })); } else if (prop.MemberType.GetInterfaces().Contains(typeof(IConvertible)) || prop.MemberType == typeof(object)) { // if the type supports IConvertible, then let SQL convert it // if the type is object, we can't do anything, so let SQL attempt to convert it } else if (!TypeHelper.IsAtomicType(prop.MemberType)) { if (mapping.Serializer != null) { var serializerMethod = mapping.Serializer.GetMethod("Serialize", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(object), typeof(Type) }, null); if (serializerMethod == null) { throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Serializer type {0} needs the method 'public static string Serialize(object, Type)'", mapping.Serializer.Name)); } il.EmitLoadType(prop.MemberType); il.Emit(OpCodes.Call, serializerMethod); } else if (dbParameter.DbType != DbType.Object) { // it's not a system type and it's not IConvertible, so let's add it as a string and let the data engine convert it. il.Emit(OpCodes.Callvirt, prop.MemberType.GetMethod("ToString", new Type[] { })); } // if we touched the value, it's possible that serializing returns a null if (dbParameter.DbType != DbType.Object) { Label internalValueIsNotNull = il.DefineLabel(); // check to see if it's not null il.Emit(OpCodes.Dup); il.Emit(OpCodes.Brtrue_S, internalValueIsNotNull); // it's null. replace the value with DbNull il.Emit(OpCodes.Pop); il.Emit(OpCodes.Ldsfld, _dbNullValue); il.MarkLabel(internalValueIsNotNull); } } /////////////////////////////////////////////////////////////// // p.Value = value /////////////////////////////////////////////////////////////// // push parameter is at top of method // value is above il.MarkLabel(readyToSetLabel); if (prop.MemberType == typeof(string)) { il.Emit(OpCodes.Call, typeof(DbParameterGenerator).GetMethod("SetParameterStringValue", BindingFlags.NonPublic | BindingFlags.Static)); } else { il.Emit(OpCodes.Callvirt, _iDataParameterSetValue); } } il.Emit(OpCodes.Ret); return((Action <IDbCommand, object>)dm.CreateDelegate(typeof(Action <IDbCommand, object>))); }
private static DynamicMethod CreateClassDeserializerDynamicMethod(Type type, IDataReader reader, IRecordStructure structure, int startColumn, int columnCount, bool createNewObject, bool isRootObject, bool allowBindChild) { // if there are no columns detected for the class, then the deserializer is null if (columnCount == 0 && !isRootObject) { return(null); } var mappings = MapColumns(type, reader, startColumn, columnCount, structure, allowBindChild && isRootObject); // need to know the constructor for the object (except for structs) bool isStruct = type.GetTypeInfo().IsValueType; ConstructorInfo constructor = createNewObject ? SelectConstructor(type) : null; // the method can either be: // createNewObject => Func<IDataReader, T> // !createNewObject => Func<IDataReader, T, T> // create a new anonymous method that takes an IDataReader and returns the given type var dm = new DynamicMethod( String.Format(CultureInfo.InvariantCulture, "Deserialize-{0}-{1}", type.FullName, Guid.NewGuid()), type, createNewObject ? new[] { typeof(IDataReader) } : new[] { typeof(IDataReader), type }, true); // get the il generator and put some local variables on the stack var il = dm.GetILGenerator(); var localIndex = il.DeclareLocal(typeof(int)); var localResult = il.DeclareLocal(type); var localValue = il.DeclareLocal(typeof(object)); var localIsNotAllDbNull = il.DeclareLocal(typeof(bool)); // initialization il.Emit(OpCodes.Ldc_I4_0); il.Emit(OpCodes.Stloc, localIndex); ///////////////////////////////////////////////////////////////////// // read all of the values into local variables ///////////////////////////////////////////////////////////////////// il.BeginExceptionBlock(); var localValues = new LocalBuilder[mappings.Count]; for (int index = 0; index < columnCount; index++) { var mapping = mappings[index]; if (mapping == null) { continue; } var member = mapping.Member; localValues[index] = il.DeclareLocal(member.MemberType); // need to call IDataReader.GetItem to get the value of the field il.Emit(OpCodes.Ldarg_0); IlHelper.EmitLdInt32(il, index + startColumn); // before we call it, put the current index into the index local variable il.Emit(OpCodes.Dup); il.Emit(OpCodes.Stloc, localIndex); // now call it il.Emit(OpCodes.Callvirt, _iDataReaderGetItem); // if handling a subobject, we check to see if the value is null if (startColumn > 0) { var afterNullCheck = il.DefineLabel(); il.Emit(OpCodes.Dup); il.Emit(OpCodes.Ldsfld, typeof(DBNull).GetField("Value")); il.Emit(OpCodes.Ceq); il.Emit(OpCodes.Brtrue, afterNullCheck); il.Emit(OpCodes.Ldc_I4_1); il.Emit(OpCodes.Stloc, localIsNotAllDbNull); il.MarkLabel(afterNullCheck); } // store the value as a local variable in case type conversion fails il.Emit(OpCodes.Dup); il.Emit(OpCodes.Stloc, localValue); // convert the value and store it locally Type sourceType = reader.GetFieldType(index + startColumn); TypeConverterGenerator.EmitConvertValue(il, member.Name, sourceType, member.MemberType, mapping.Serializer); il.Emit(OpCodes.Stloc, localValues[index]); } ///////////////////////////////////////////////////////////////////// // catch translation exceptions and rethrow ///////////////////////////////////////////////////////////////////// il.BeginCatchBlock(typeof(Exception)); // stack => [Exception] il.Emit(OpCodes.Ldloc, localIndex); // push loc.0, stack => [Exception][index] il.Emit(OpCodes.Ldarg_0); // push arg.0, stack => [Exception][index][reader] il.Emit(OpCodes.Ldloc, localValue); // push loc.3, stack => [Exception][index][reader][value] il.Emit(OpCodes.Call, TypeConverterGenerator.CreateDataExceptionMethod); il.Emit(OpCodes.Throw); // stack => DataException il.EndExceptionBlock(); ///////////////////////////////////////////////////////////////////// // if this was a subobject and all of the values are null, then return the default for the object ///////////////////////////////////////////////////////////////////// if (startColumn > 0) { var afterNullExit = il.DefineLabel(); il.Emit(OpCodes.Ldloc, localIsNotAllDbNull); il.Emit(OpCodes.Brtrue, afterNullExit); TypeHelper.EmitDefaultValue(il, type); il.Emit(OpCodes.Ret); il.MarkLabel(afterNullExit); } ///////////////////////////////////////////////////////////////////// // call the constructor ///////////////////////////////////////////////////////////////////// if (createNewObject) { if (isStruct) { il.Emit(OpCodes.Ldloca_S, localResult); il.Emit(OpCodes.Initobj, type); if (constructor != null) { il.Emit(OpCodes.Ldloca_S, localResult); } } // if there is a constructor, then populate the values if (constructor != null) { foreach (var p in constructor.GetParameters()) { var mapping = mappings.Where(m => m != null).SingleOrDefault(m => m.Member.Name.IsIEqualTo(p.Name)); if (mapping != null) { il.Emit(OpCodes.Ldloc, localValues[mappings.IndexOf(mapping)]); } else { TypeHelper.EmitDefaultValue(il, p.ParameterType); } } } if (isStruct) { if (constructor != null) { il.Emit(OpCodes.Call, constructor); } } else { il.Emit(OpCodes.Newobj, constructor); il.Emit(OpCodes.Stloc, localResult); } } else { il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Stloc, localResult); } ///////////////////////////////////////////////////////////////////// // for anything not passed to the constructor, copy the local values to the properties ///////////////////////////////////////////////////////////////////// for (int index = 0; index < columnCount; index++) { var mapping = mappings[index]; if (mapping == null) { continue; } var member = mapping.Member; if (!member.CanSetMember) { continue; } // don't set values that have already been set if (constructor != null && constructor.GetParameters().Any(p => p.Name.IsIEqualTo(mapping.Member.Name))) { continue; } // load the address of the object we are working on if (isStruct) { il.Emit(OpCodes.Ldloca_S, localResult); } else { il.Emit(OpCodes.Ldloc, localResult); } // for deep mappings, go to the parent of the field that we are trying to set var nextLabel = il.DefineLabel(); if (mapping.IsDeep) { ClassPropInfo.EmitGetValue(type, mapping.Prefix, il); // if the mapping parent is nullable, check to see if it is null. // if so, pop the parent off the stack and move to the next field if (!ClassPropInfo.FindMember(type, mapping.Prefix).MemberType.GetTypeInfo().IsValueType) { var notNullLabel = il.DefineLabel(); il.Emit(OpCodes.Dup); il.Emit(OpCodes.Brtrue, notNullLabel); il.Emit(OpCodes.Pop); il.Emit(OpCodes.Br, nextLabel); il.MarkLabel(notNullLabel); } } // load the value from the local and set it on the object il.Emit(OpCodes.Ldloc, localValues[index]); member.EmitSetValue(il); il.MarkLabel(nextLabel); ///////////////////////////////////////////////////////////////////// // stack should be [target] and ready for the next column ///////////////////////////////////////////////////////////////////// } ///////////////////////////////////////////////////////////////////// // load the return value from the local variable ///////////////////////////////////////////////////////////////////// il.Emit(OpCodes.Ldloc, localResult); // ld loc.1 (target), stack => [target] il.Emit(OpCodes.Ret); // create the function return(dm); }
private static Action <IDbCommand, object> CreateClassInputParameterGenerator(IDbCommand command, Type type) { var provider = InsightDbProvider.For(command); var parameters = provider.DeriveParameters(command); // special case if the parameters object is an IEnumerable or Array // look for the parameter that is a Structured object and pass the array to the TVP // note that string supports ienumerable, so exclude atomic types var enumerable = type.GetInterfaces().FirstOrDefault(i => i.GetTypeInfo().IsGenericType&& i.GetGenericTypeDefinition() == typeof(IEnumerable <>)); if (enumerable != null && type != typeof(string) && parameters.OfType <IDataParameter>().Where(p => p.Direction.HasFlag(ParameterDirection.Input)).Count() == 1) { return((IDbCommand cmd, object o) => { // don't use the provider above. The command may be unwrapped by the time we get back here var tableParameter = InsightDbProvider.For(cmd).CloneParameter(cmd, parameters.OfType <IDataParameter>().Single(p => p.Direction.HasFlag(ParameterDirection.Input))); cmd.Parameters.Add(tableParameter); ListParameterHelper.ConvertListParameter(tableParameter, o, cmd); }); } // get the mapping of the properties for the type var mappings = ColumnMapping.MapParameters(type, command, parameters); // start creating a dynamic method Type typeOwner = type.HasElementType ? type.GetElementType() : type; var dm = new DynamicMethod(String.Format(CultureInfo.InvariantCulture, "CreateInputParameters-{0}", Guid.NewGuid()), null, new[] { typeof(IDbCommand), typeof(object) }, typeOwner, true); var il = dm.GetILGenerator(); // copy the parameters into the command object var parametersLocal = il.DeclareLocal(typeof(IDataParameter[])); StaticFieldStorage.EmitLoad(il, provider); il.Emit(OpCodes.Ldarg_0); StaticFieldStorage.EmitLoad(il, parameters); il.Emit(OpCodes.Call, typeof(InsightDbProvider).GetMethod("CopyParameters", BindingFlags.NonPublic | BindingFlags.Instance)); il.Emit(OpCodes.Stloc, parametersLocal); // go through all of the mappings for (int i = 0; i < mappings.Count; i++) { var mapping = mappings[i]; var dbParameter = parameters[i]; // if there is no mapping for the parameter if (mapping == null) { // sql will silently eat table parameters that are not specified, and that can be difficult to debug if (provider.IsTableValuedParameter(command, dbParameter)) { throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Table parameter {0} must be specified", dbParameter.ParameterName)); } // unspecified input parameters get skipped if (dbParameter.Direction == ParameterDirection.Input) { parameters[i] = null; } continue; } var memberType = mapping.Member.MemberType; var serializer = mapping.Serializer; // get the parameter il.Emit(OpCodes.Ldloc, parametersLocal); il.Emit(OpCodes.Ldc_I4, i); il.Emit(OpCodes.Ldelem, typeof(IDataParameter)); // look up the best type to use for the parameter DbType sqlType = LookupDbType(memberType, serializer, dbParameter.DbType); // give the provider an opportunity to fix up the template parameter (e.g. set UDT type names) provider.FixupParameter(command, dbParameter, sqlType, memberType, mapping.Member.SerializationMode); // give a chance to override the best guess parameter DbType overriddenSqlType = sqlType; if (sqlType != DbTypeEnumerable) { overriddenSqlType = ColumnMapping.MapParameterDataType(memberType, command, dbParameter, sqlType); } /////////////////////////////////////////////////////////////// // We have a parameter, start handling all of the other types /////////////////////////////////////////////////////////////// if (overriddenSqlType != sqlType) { sqlType = overriddenSqlType; dbParameter.DbType = sqlType; } /////////////////////////////////////////////////////////////// // Get the value from the object onto the stack /////////////////////////////////////////////////////////////// il.Emit(OpCodes.Ldarg_1); if (type.GetTypeInfo().IsValueType) { il.Emit(OpCodes.Unbox_Any, type); } /////////////////////////////////////////////////////////////// // Special case support for enumerables. If the type is -1 (our workaround, then call the list parameter method) /////////////////////////////////////////////////////////////// if (sqlType == DbTypeEnumerable) { // we have the parameter and the value as object, add the command ClassPropInfo.EmitGetValue(type, mapping.PathToMember, il); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Call, typeof(ListParameterHelper).GetMethod("ConvertListParameter", BindingFlags.Static | BindingFlags.NonPublic)); continue; } Label readyToSetLabel = il.DefineLabel(); ClassPropInfo.EmitGetValue(type, mapping.PathToMember, il, readyToSetLabel); // special conversions for timespan to datetime if ((sqlType == DbType.Time && dbParameter.DbType != DbType.Time) || (dbParameter.DbType == DbType.DateTime || dbParameter.DbType == DbType.DateTime2 || dbParameter.DbType == DbType.DateTimeOffset)) { IlHelper.EmitLdInt32(il, (int)dbParameter.DbType); il.Emit(OpCodes.Call, typeof(TypeConverterGenerator).GetMethod("ObjectToSqlDateTime")); } // if it's class type, boxed value type (in an object), or nullable, then we have to check for null var nullableUnderlyingType = Nullable.GetUnderlyingType(memberType); if (!memberType.GetTypeInfo().IsValueType || nullableUnderlyingType != null) { Label notNull = il.DefineLabel(); // check to see if it's not null il.Emit(OpCodes.Dup); il.Emit(OpCodes.Brtrue, notNull); // it's null. replace the value with DbNull il.Emit(OpCodes.Pop); il.Emit(OpCodes.Ldsfld, _dbNullValue); // value is set to null. ready to set the property. il.Emit(OpCodes.Br, readyToSetLabel); // we know the value is not null il.MarkLabel(notNull); } // some providers (notably npgsql > 4.0) don't convert enums to ints, so we do it for them if ((memberType != null && memberType.GetTypeInfo() != null && memberType.GetTypeInfo().IsEnum) || (nullableUnderlyingType != null && nullableUnderlyingType.GetTypeInfo() != null && nullableUnderlyingType.GetTypeInfo().IsEnum)) { var enumType = nullableUnderlyingType ?? memberType; // ClassPropInfo.EmitGetValue has the enum boxed, so unbox, cast, and re-box switch (dbParameter.DbType) { case DbType.Int16: il.Emit(OpCodes.Unbox_Any, enumType); il.Emit(OpCodes.Conv_I2); il.Emit(OpCodes.Box, typeof(Int16)); break; case DbType.Int32: il.Emit(OpCodes.Unbox_Any, enumType); il.Emit(OpCodes.Conv_I4); il.Emit(OpCodes.Box, typeof(Int32)); break; case DbType.Int64: il.Emit(OpCodes.Unbox_Any, enumType); il.Emit(OpCodes.Conv_I8); il.Emit(OpCodes.Box, typeof(Int64)); break; } } /////////////////////////////////////////////////////////////// // if this is a linq binary, convert it to a byte array /////////////////////////////////////////////////////////////// if (memberType == TypeHelper.LinqBinaryType) { il.Emit(OpCodes.Callvirt, TypeHelper.LinqBinaryToArray); } else if (memberType == typeof(XmlDocument)) { // we are sending up an XmlDocument. ToString just returns the classname, so use the outerxml. il.Emit(OpCodes.Callvirt, memberType.GetProperty("OuterXml").GetGetMethod()); } else if (memberType == typeof(XDocument)) { // we are sending up an XDocument. Use ToString. il.Emit(OpCodes.Callvirt, memberType.GetMethod("ToString", new Type[] { })); } else if (serializer != null && serializer.CanSerialize(memberType, sqlType)) { il.EmitLoadType(memberType); StaticFieldStorage.EmitLoad(il, serializer); il.Emit(OpCodes.Call, typeof(DbParameterGenerator).GetMethod("SerializeParameterValue", BindingFlags.NonPublic | BindingFlags.Static)); } /////////////////////////////////////////////////////////////// // p.Value = value /////////////////////////////////////////////////////////////// // push parameter is at top of method // value is above il.MarkLabel(readyToSetLabel); if (memberType == typeof(string)) { il.Emit(OpCodes.Call, typeof(DbParameterGenerator).GetMethod("SetParameterStringValue", BindingFlags.NonPublic | BindingFlags.Static)); } else if ((memberType == typeof(Guid?) || (memberType == typeof(Guid))) && dbParameter.DbType != DbType.Guid && command.CommandType == CommandType.StoredProcedure) { il.Emit(OpCodes.Call, typeof(DbParameterGenerator).GetMethod("SetParameterGuidValue", BindingFlags.NonPublic | BindingFlags.Static)); } else { il.Emit(OpCodes.Callvirt, _iDataParameterSetValue); } } il.Emit(OpCodes.Ret); return((Action <IDbCommand, object>)dm.CreateDelegate(typeof(Action <IDbCommand, object>))); }
private static bool EmitCoersion(ILGenerator il, Type sourceType, Type targetType) { // support auto-converting strings to other types by parsing if (sourceType == typeof(string)) { var parseMethod = targetType.GetMethod("Parse", new Type[] { typeof(string) }); if (parseMethod != null) { il.Emit(OpCodes.Call, parseMethod); return(true); } } // if we are converting to a string, use the default ToString on the object if (targetType == typeof(string)) { IlHelper.EmitToStringOrNull(il, sourceType); return(true); } if (!sourceType.IsPrimitive) { return(false); } if (!targetType.IsPrimitive) { return(false); } // if the enum is based on a different type of integer than returned, then do the conversion if (targetType == typeof(Int32) && sourceType != typeof(Int32)) { il.Emit(OpCodes.Conv_I4); } else if (targetType == typeof(Int64) && sourceType != typeof(Int64)) { il.Emit(OpCodes.Conv_I8); } else if (targetType == typeof(Int16) && sourceType != typeof(Int16)) { il.Emit(OpCodes.Conv_I2); } else if (targetType == typeof(char) && sourceType != typeof(char)) { il.Emit(OpCodes.Conv_I1); } else if (targetType == typeof(sbyte) && sourceType != typeof(sbyte)) { il.Emit(OpCodes.Conv_I1); } else if (targetType == typeof(UInt32) && sourceType != typeof(UInt32)) { il.Emit(OpCodes.Conv_U4); } else if (targetType == typeof(UInt64) && sourceType != typeof(UInt64)) { il.Emit(OpCodes.Conv_U8); } else if (targetType == typeof(UInt16) && sourceType != typeof(UInt16)) { il.Emit(OpCodes.Conv_U2); } else if (targetType == typeof(byte) && sourceType != typeof(byte)) { il.Emit(OpCodes.Conv_U1); } else if (targetType == typeof(bool) && sourceType != typeof(bool)) { il.Emit(OpCodes.Conv_U1); } else if (targetType == typeof(double) && sourceType != typeof(double)) { il.Emit(OpCodes.Conv_R8); } else if (targetType == typeof(float) && sourceType != typeof(float)) { il.Emit(OpCodes.Conv_R4); } return(true); }
/// <summary> /// Compiles and returns a method that deserializes class type from the subset of fields of an IDataReader record. /// </summary> /// <param name="type">The type of object to deserialize.</param> /// <param name="reader">The reader to analyze.</param> /// <param name="structure">The structure of the record being read.</param> /// <param name="startColumn">The index of the first column to read.</param> /// <param name="columnCount">The number of columns to read.</param> /// <param name="createNewObject">True if the method should create a new instance of an object, false to have the object passed in as a parameter.</param> /// <param name="isRootObject">True if this object is the root object and should always be created.</param> /// <returns>If createNewObject=true, then Func<IDataReader, T>.</returns> /// <remarks>This returns a DynamicMethod so that the graph deserializer can call the methods using IL. IL cannot call the dm after it is converted to a delegate.</remarks> private static DynamicMethod CreateClassDeserializerDynamicMethod(Type type, IDataReader reader, IRecordStructure structure, int startColumn, int columnCount, bool createNewObject, bool isRootObject) { // if there are no columns detected for the class, then the deserializer is null if (columnCount == 0 && !isRootObject) { return(null); } // get the mapping from the reader to the type var mapping = ColumnMapping.Tables.CreateMapping(type, reader, null, null, structure, startColumn, columnCount, true); // need to know the constructor for the object (except for structs) bool isStruct = type.IsValueType; ConstructorInfo constructor = null; if (!isStruct) { constructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if (constructor == null) { throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Cannot find a default constructor for type {0}", type.FullName)); } } // the method can either be: // createNewObject => Func<IDataReader, T> // !createNewObject => Func<IDataReader, T, T> // create a new anonymous method that takes an IDataReader and returns the given type var dm = new DynamicMethod( String.Format(CultureInfo.InvariantCulture, "Deserialize-{0}-{1}", type.FullName, Guid.NewGuid()), type, createNewObject ? new[] { typeof(IDataReader) } : new[] { typeof(IDataReader), type }, true); // get the il generator and put some local variables on the stack var il = dm.GetILGenerator(); var localIndex = il.DeclareLocal(typeof(int)); var localResult = il.DeclareLocal(type); var localValue = il.DeclareLocal(typeof(object)); var localIsNotAllDbNull = il.DeclareLocal(typeof(bool)); // initialize index = 0 il.Emit(OpCodes.Ldc_I4_0); il.Emit(OpCodes.Stloc, localIndex); // emit a call to the constructor of the object il.BeginExceptionBlock(); // if we are supposed to create a new object, then new that up, otherwise use the object passed in as an argument // this block sets loc.1 with the return value if (isStruct) { if (createNewObject) { il.Emit(OpCodes.Ldloca_S, localResult); // load the pointer to the result on the stack il.Emit(OpCodes.Initobj, type); // initialize the object on the stack } else { il.Emit(OpCodes.Ldarg_1); // store arg.1 => loc.1 il.Emit(OpCodes.Stloc, localResult); } } else { if (createNewObject) { il.Emit(OpCodes.Newobj, constructor); // push new T, stack => [target] } else { il.Emit(OpCodes.Ldarg_1); // push arg.1 (T), stack => [target] } il.Emit(OpCodes.Stloc, localResult); // pop loc.1 (result), stack => [empty] } var returnLabel = il.DefineLabel(); for (int index = 0; index < columnCount; index++) { // if there is no matching property for this column, then continue if (mapping[index] == null) { continue; } var method = mapping[index].ClassPropInfo; if (!method.CanSetMember) { continue; } // store the value as a local variable in case type conversion fails il.Emit(OpCodes.Ldnull); il.Emit(OpCodes.Stloc, localValue); // load the address of the object we are working on if (isStruct) { il.Emit(OpCodes.Ldloca_S, localResult); // push pointer to object } else { il.Emit(OpCodes.Ldloc, localResult); // push loc.1 (target), stack => [target] } // need to call IDataReader.GetItem to get the value of the field il.Emit(OpCodes.Ldarg_0); // push arg.0 (reader), stack => [target][reader] IlHelper.EmitLdInt32(il, index + startColumn); // push index, stack => [target][reader][index] // before we call it, put the current index into the index local variable il.Emit(OpCodes.Dup); // dup index, stack => [target][reader][index][index] il.Emit(OpCodes.Stloc, localIndex); // pop loc.0 (index), stack => [target][reader][index] // now call it il.Emit(OpCodes.Callvirt, _iDataReaderGetItem); // call getItem, stack => [target][value-as-object] // store the value as a local variable in case type conversion fails il.Emit(OpCodes.Dup); il.Emit(OpCodes.Stloc, localValue); ///////////////////////////////////////////////////////////////////// // if this a subobject, then check to see if the value is null ///////////////////////////////////////////////////////////////////// if (startColumn > 0) { var afterNullCheck = il.DefineLabel(); il.Emit(OpCodes.Dup); il.Emit(OpCodes.Ldsfld, typeof(DBNull).GetField("Value")); il.Emit(OpCodes.Ceq); il.Emit(OpCodes.Brtrue, afterNullCheck); il.Emit(OpCodes.Ldc_I4_1); il.Emit(OpCodes.Stloc, localIsNotAllDbNull); il.MarkLabel(afterNullCheck); } // determine the type of the object in the recordset Type sourceType = reader.GetFieldType(index + startColumn); // emit the code to convert the value and set the value on the field Label finishLabel = TypeConverterGenerator.EmitConvertAndSetValue(il, sourceType, mapping[index]); ///////////////////////////////////////////////////////////////////// // stack should be [target] and ready for the next column ///////////////////////////////////////////////////////////////////// il.MarkLabel(finishLabel); } ///////////////////////////////////////////////////////////////////// // if this was a subobject and all of the values are null, then load the default for the object ///////////////////////////////////////////////////////////////////// if (startColumn > 0) { var afterNullExit = il.DefineLabel(); il.Emit(OpCodes.Ldloc, localIsNotAllDbNull); il.Emit(OpCodes.Brtrue, afterNullExit); TypeHelper.EmitDefaultValue(il, type); // load the default for the type il.Emit(OpCodes.Stloc, localResult); // store null => loc.1 (target) il.Emit(OpCodes.Br, returnLabel); // exit the loop il.MarkLabel(afterNullExit); } il.MarkLabel(returnLabel); ///////////////////////////////////////////////////////////////////// // catch exceptions and rethrow ///////////////////////////////////////////////////////////////////// il.BeginCatchBlock(typeof(Exception)); // stack => [Exception] il.Emit(OpCodes.Ldloc, localIndex); // push loc.0, stack => [Exception][index] il.Emit(OpCodes.Ldarg_0); // push arg.0, stack => [Exception][index][reader] il.Emit(OpCodes.Ldloc, localValue); // push loc.3, stack => [Exception][index][reader][value] il.Emit(OpCodes.Call, TypeConverterGenerator.CreateDataExceptionMethod); il.Emit(OpCodes.Throw); // stack => DataException il.EndExceptionBlock(); ///////////////////////////////////////////////////////////////////// // load the return value from the local variable ///////////////////////////////////////////////////////////////////// il.Emit(OpCodes.Ldloc, localResult); // ld loc.1 (target), stack => [target] il.Emit(OpCodes.Ret); // create the function return(dm); }