/// <summary> /// Creates a converter from output parameters to an object of a given type. /// </summary> /// <param name="command">The command to analyze for the results.</param> /// <param name="type">The type to put the values into.</param> /// <returns>The converter method.</returns> private static Action <IDbCommand, object> CreateClassOutputParameterConverter(IDbCommand command, Type type) { // get the parameters List <IDataParameter> parameters = command.Parameters.Cast <IDataParameter>().ToList(); // if there are no output parameters, then return an empty method if (!parameters.Cast <IDataParameter>().Any(p => p.Direction.HasFlag(ParameterDirection.Output))) { return (IDbCommand c, object o) => { } } ; // create a dynamic method Type typeOwner = type.HasElementType ? type.GetElementType() : type; // start creating a dynamic method var dm = new DynamicMethod(String.Format(CultureInfo.InvariantCulture, "CreateOutputParameters-{0}", Guid.NewGuid()), null, new[] { typeof(IDbCommand), typeof(object) }, typeOwner, true); var il = dm.GetILGenerator(); var localParameters = il.DeclareLocal(typeof(IDataParameterCollection)); // get the parameters collection from the command into loc.0 il.Emit(OpCodes.Ldarg_0); // push arg.0 (command), stack => [command] il.Emit(OpCodes.Callvirt, _iDbCommandGetParameters); // call getparams, stack => [parameters] il.Emit(OpCodes.Stloc, localParameters); // go through all of the mappings var mappings = ColumnMapping.Parameters.CreateMapping(type, null, command, parameters, null, 0, parameters.Count, true); for (int i = 0; i < mappings.Length; i++) { // if there is no parameter for this property, then skip it var mapping = mappings[i]; if (mapping == null) { continue; } // if the property is readonly, then skip it var prop = mapping.ClassPropInfo; if (!prop.CanSetMember) { continue; } // if the parameter is not output, then skip it IDataParameter parameter = parameters[i]; if (parameter == null || !parameter.Direction.HasFlag(ParameterDirection.Output)) { continue; } // push the object on the stack. we will need it to set the value below il.Emit(OpCodes.Ldarg_1); // get the parameter out of the collection il.Emit(OpCodes.Ldloc, localParameters); il.Emit(OpCodes.Ldstr, parameter.ParameterName); // push (parametername) il.Emit(OpCodes.Callvirt, _iDataParameterCollectionGetItem); // get the value out of the parameter il.Emit(OpCodes.Callvirt, _iDataParameterGetValue); // emit the code to convert the value and set it on the object Label finishLabel = TypeConverterGenerator.EmitConvertAndSetValue(il, _dbTypeToTypeMap[parameter.DbType], mapping); il.MarkLabel(finishLabel); } il.Emit(OpCodes.Ret); return((Action <IDbCommand, object>)dm.CreateDelegate(typeof(Action <IDbCommand, object>))); }
/// <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); }