/// <summary> /// Creates a deserializer to deserialize an object from an IDataReader. /// </summary> /// <param name="reader">The reader to analyze.</param> /// <param name="type">The type of object to deserialize from the IDataReader.</param> /// <param name="structure">The structure of the object.</param> /// <param name="mappingType">The type of mapping to create.</param> /// <returns> /// A function that takes an IDataReader and deserializes an object of type T. /// The first parameter will be an IDataReader. /// If createNewObject is true, the next parameter will be of type T. /// If useCallback is true, the next parameter will be an Action<object[]>. /// </returns> public static Delegate CreateDeserializer(IDataReader reader, Type type, IRecordStructure structure, SchemaMappingType mappingType) { // process the object graph types var subTypes = structure.GetObjectTypes(); if (subTypes[0] != type) { throw new ArgumentException("The top-level type of the object graph must match the return type of the object.", "structure"); } // if the graph type is not a graph, or just the object, and we don't want a callback function // then just return a one-level graph. if (subTypes.Length == 1 && !mappingType.HasFlag(SchemaMappingType.WithCallback)) { return(CreateClassDeserializer(type, reader, structure, 0, (reader.IsClosed) ? 0 : reader.FieldCount, mappingType.HasFlag(SchemaMappingType.NewObject))); } // we can't deserialize an object graph in an insert/merge because we don't know whether to create subobjects or leave them null. if (!mappingType.HasFlag(SchemaMappingType.NewObject)) { throw new ArgumentException("mappingType must be set to NewObject when deserializing an object graph.", "mappingType"); } // create the graph deserializer if (mappingType.HasFlag(SchemaMappingType.WithCallback)) { return(CreateGraphDeserializerWithCallback(subTypes, reader, structure, mappingType == SchemaMappingType.ExistingObject)); } else { return(CreateGraphDeserializer(subTypes, reader, structure, mappingType == SchemaMappingType.ExistingObject)); } }
/// <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> /// <returns>If createNewObject=true, then Func<IDataReader, T>.</returns> private static Delegate CreateClassDeserializer(Type type, IDataReader reader, IRecordStructure structure, int startColumn, int columnCount, bool createNewObject) { var method = CreateClassDeserializerDynamicMethod(type, reader, structure, startColumn, columnCount, createNewObject, true); // create a generic type for the delegate we are returning Type delegateType; if (createNewObject) delegateType = typeof(Func<,>).MakeGenericType(typeof(IDataReader), type); else delegateType = typeof(Func<,,>).MakeGenericType(typeof(IDataReader), type, type); return method.CreateDelegate(delegateType); }
/// <summary> /// Get a deserializer to read class T from the given reader. /// </summary> /// <param name="reader">The reader to read from.</param> /// <param name="type">The type of object to deserialize.</param> /// <param name="structure">The structure of the objects in the record.</param> /// <param name="mappingType">The type of mapping to return.</param> /// <returns>A function that can deserialize a T from the reader.</returns> private static Delegate GetDeserializer(IDataReader reader, Type type, IRecordStructure structure, SchemaMappingType mappingType) { if (structure == null) { throw new ArgumentNullException("structure"); } // This method should try to return the deserializer with as little work as possible. // Calculating the SchemaMappingIdentity is relatively expensive, so we will take care of the simple cases first, // Where we can just look up a type in a dictionary. // since these types are single column types, deserializing these types do not depend on the schema that comes back from the database // we don't need to keep a schema identity for these Delegate deserializer = null; if (!_simpleDeserializers.TryGetValue(type, out deserializer) && TypeHelper.IsAtomicType(type)) { deserializer = GetValueDeserializer(type); } // we have a simple deserializer if (deserializer != null) { var genericArgs = structure.GetObjectTypes(); if (genericArgs.Length != 1 || genericArgs[0] != type) { throw new ArgumentException("Column Mapper does not match single column deserialization", "structure"); } return(deserializer); } // at this point, we know that we aren't returning a value type or simple object that doesn't depend on the schema. // so we need to calculate a mapping identity and then create or return a deserializer. SchemaMappingIdentity identity = new SchemaMappingIdentity(reader, structure, mappingType); // try to get the deserializer. if not found, create one. return(_deserializers.GetOrAdd( identity, key => ClassDeserializerGenerator.CreateDeserializer(reader, type, structure, mappingType))); }
/// <summary> /// Creates a deserializer to deserialize an object from an IDataReader. /// </summary> /// <param name="reader">The reader to analyze.</param> /// <param name="type">The type of object to deserialize from the IDataReader.</param> /// <param name="structure">The structure of the object.</param> /// <param name="mappingType">The type of mapping to create.</param> /// <returns> /// A function that takes an IDataReader and deserializes an object of type T. /// The first parameter will be an IDataReader. /// If createNewObject is true, the next parameter will be of type T. /// If useCallback is true, the next parameter will be an Action<object[]>. /// </returns> public static Delegate CreateDeserializer(IDataReader reader, Type type, IRecordStructure structure, SchemaMappingType mappingType) { // process the object graph types var subTypes = structure.GetObjectTypes(); if (subTypes[0] != type) throw new ArgumentException("The top-level type of the object graph must match the return type of the object.", "structure"); // if the graph type is not a graph, or just the object, and we don't want a callback function // then just return a one-level graph. if (subTypes.Length == 1 && !mappingType.HasFlag(SchemaMappingType.WithCallback)) return CreateClassDeserializer(type, reader, structure, 0, (reader.IsClosed) ? 0 : reader.FieldCount, mappingType.HasFlag(SchemaMappingType.NewObject)); // we can't deserialize an object graph in an insert/merge because we don't know whether to create subobjects or leave them null. if (!mappingType.HasFlag(SchemaMappingType.NewObject)) throw new ArgumentException("mappingType must be set to NewObject when deserializing an object graph.", "mappingType"); // create the graph deserializer if (mappingType.HasFlag(SchemaMappingType.WithCallback)) return CreateGraphDeserializerWithCallback(subTypes, reader, structure); else return CreateGraphDeserializer(subTypes, reader, structure); }
/// <summary> /// Detect the boundary between tOther and tNext in the reader. /// </summary> /// <param name="reader">The reader to analyze.</param> /// <param name="structure">The structure of the record we are reading.</param> /// <param name="columnIndex">The index of the next column to look at.</param> /// <param name="types">The list of types to be deserialized.</param> /// <param name="typeIndex">The index of the current type being deserialized.</param> /// <returns>The end boundary for the current object.</returns> private static int DetectEndColumn( IDataReader reader, IRecordStructure structure, int columnIndex, Type[] types, int typeIndex) { Type currentType = types[typeIndex]; Type nextType = (typeIndex + 1 < types.Length) ? types[typeIndex + 1] : typeof(object); // if the current type is atomic, it's only one column wide if (TypeHelper.IsAtomicType(currentType)) { return(columnIndex + 1); } // if the caller specified columns to split on then use that var splitColumns = structure.GetSplitColumns(); if (splitColumns != null) { // go through all of the remaining types for (int t = typeIndex + 1; t < types.Length; t++) { // get the column name for the id of the next type string columnName; if (!splitColumns.TryGetValue(types[t], out columnName)) { continue; } for (; columnIndex < reader.FieldCount; columnIndex++) { if (String.Compare(columnName, reader.GetName(columnIndex), StringComparison.OrdinalIgnoreCase) == 0) { return(columnIndex); } } } } // get the setters for the class and the next class // for the current set, we want to simulate what we will actually use, so we only want to use unique matches // for the next set, we want to find all applicable matches, so we can detect the transition to the next object int fieldCount = reader.FieldCount; int columnsLeft = fieldCount - columnIndex; var currentSetters = MapColumns(currentType, reader, columnIndex, columnsLeft, structure); // go through the remaining types to see if anything will claim the column int i = 0; for (; columnIndex + i < fieldCount; i++) { // if there is a setter for the current column, keep going if (currentSetters[i] != null) { continue; } // there isn't a setting for the column, so see if any other type can claim the column for (int t = typeIndex + 1; t < types.Length; t++) { // if the next type is an atomic type, then read it if (TypeHelper.IsAtomicType(types[t])) { return(columnIndex + i); } // one of the next types can claim the column, so quit now var nextSetters = MapColumns(types[t], reader, columnIndex + i, 1, structure); if (nextSetters[0] != null) { return(columnIndex + i); } } } return(columnIndex + i); }
/// <summary> /// Create the deserializers for all of the sub-object types in the graph. /// </summary> /// <param name="subTypes">The list of sub-object types to parse.</param> /// <param name="reader">The reader to analyze.</param> /// <param name="structure">The structure of the record we are reading.</param> /// <param name="allowBindChild">True if the columns should be allowed to bind to children.</param> /// <returns>An array of delegates.</returns> private static DynamicMethod[] CreateDeserializersForSubObjects(Type[] subTypes, IDataReader reader, IRecordStructure structure, bool allowBindChild) { // take up to the first NoClass int subTypeCount = subTypes.Length; DynamicMethod[] deserializers = new DynamicMethod[subTypeCount]; int column = 0; for (int i = 0; i < subTypeCount; i++) { // determine the end of this type and the beginning of the next int endColumn = DetectEndColumn(reader, structure, column, subTypes, i); // generate a deserializer for the class var subType = subTypes[i]; if (TypeHelper.IsAtomicType(subType)) { deserializers[i] = CreateValueDeserializer(subType, column); } else { deserializers[i] = CreateClassDeserializerDynamicMethod(subType, reader, structure, column, endColumn - column, true, (i == 0), (i == 0) && allowBindChild); } column = endColumn; } return(deserializers); }
/// <summary> /// Creates a deserializer for a graph of objects. The objects are allocated to an array of objects and passed to a callback that assembles the objects. /// </summary> /// <param name="subTypes">The types of the subobjects.</param> /// <param name="reader">The reader to analyze.</param> /// <param name="structure">The structure of the record we are reading.</param> /// <param name="allowBindChild">True if the columns should be allowed to bind to children.</param> /// <returns>A function that takes an IDataReader and deserializes an object of type T.</returns> private static Delegate CreateGraphDeserializerWithCallback(Type[] subTypes, IDataReader reader, IRecordStructure structure, bool allowBindChild) { Type type = subTypes[0]; // go through each of the subtypes var deserializers = CreateDeserializersForSubObjects(subTypes, reader, structure, allowBindChild); // create a new anonymous method that takes an IDataReader and returns T var dm = new DynamicMethod( String.Format(CultureInfo.InvariantCulture, "Deserialize-{0}-{1}", type.FullName, Guid.NewGuid()), type, new[] { typeof(IDataReader), typeof(Action <object[]>) }, true); var il = dm.GetILGenerator(); // we have a callback function to call with our objects, so set up the delegate and the array var localObject = il.DeclareLocal(type); // store the result il.Emit(OpCodes.Ldarg_1); // push the delegate il.Emit(OpCodes.Ldc_I4, deserializers.Length); // create a new array il.Emit(OpCodes.Newarr, typeof(object)); /////////////////////////////////////////////////// // emit the method /////////////////////////////////////////////////// for (int i = 0; i < deserializers.Length; i++) { il.Emit(OpCodes.Dup); // duplicate the array il.Emit(OpCodes.Ldc_I4, i); // the index to store to // call the deserializer for the subobject il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Call, deserializers[i]); // for the root object, store it in our local variable if (i == 0) { il.Emit(OpCodes.Dup); il.Emit(OpCodes.Stloc, localObject); } il.Emit(OpCodes.Stelem, typeof(object)); // store the element in the array } // we have a callback function to call with our objects, so set up the delegate and the array il.Emit(OpCodes.Call, typeof(Action <object[]>).GetMethod("Invoke")); // invoke the delegate // return the result il.Emit(OpCodes.Ldloc, localObject); // put the root object back in for return il.Emit(OpCodes.Ret); // convert the dynamic method to a delegate var delegateType = typeof(Func <, ,>).MakeGenericType(typeof(IDataReader), typeof(Action <object[]>), type); return(dm.CreateDelegate(delegateType)); }
/// <summary> /// Creates a deserializer for a graph of objects. /// </summary> /// <param name="subTypes">The types of the subobjects.</param> /// <param name="reader">The reader to analyze.</param> /// <param name="structure">The structure of the record we are reading.</param> /// <param name="allowBindChild">True if the columns should be allowed to bind to children.</param> /// <returns>A function that takes an IDataReader and deserializes an object of type T.</returns> private static Delegate CreateGraphDeserializer(Type[] subTypes, IDataReader reader, IRecordStructure structure, bool allowBindChild) { Type type = subTypes[0]; bool isStruct = type.GetTypeInfo().IsValueType; // go through each of the subtypes var deserializers = CreateDeserializersForSubObjects(subTypes, reader, structure, allowBindChild); // create a new anonymous method that takes an IDataReader and returns T var dm = new DynamicMethod( String.Format(CultureInfo.InvariantCulture, "Deserialize-{0}-{1}", type.FullName, Guid.NewGuid()), type, new[] { typeof(IDataReader) }, true); var il = dm.GetILGenerator(); var localObject = il.DeclareLocal(type); // keep track of the properties that we have already used // the tuple is the level + property var usedMethods = new HashSet <Tuple <int, ClassPropInfo> >(); /////////////////////////////////////////////////// // emit the method /////////////////////////////////////////////////// for (int i = 0; i < deserializers.Length; i++) { // if there is no deserializer for this object, then skip it if (deserializers[i] == null) { continue; } // for subobjects, dup the core object so we can set values on it if (i > 0) { if (isStruct) { il.Emit(OpCodes.Ldloca_S, localObject); } else { il.Emit(OpCodes.Ldloc, localObject); } } // if we don't have a callback, then we are going to store the value directly into the field on T or one of the subobjects // here we determine the proper set method to store into. // we are going to look into all of the types in the graph and find the first parameter that matches our current type ClassPropInfo setMethod = null; for (int parent = 0; parent < i; parent++) { // find the set method on the current parent setMethod = GetFirstMatchingMethod(ClassPropInfo.GetMembersForType(subTypes[parent]).Where(m => m.CanSetMember), subTypes[i]); // make sure that at a given level, we only use the method once var tuple = Tuple.Create(parent, setMethod); if (usedMethods.Contains(tuple)) { continue; } else { usedMethods.Add(tuple); } // if we didn't find a matching set method, then continue on to the next type in the graph if (setMethod == null) { continue; } // if the parent is not the root object, we have to drill down to the parent, then set the value // the root object is already on the stack, so emit a get method to get the object to drill down into for (int p = 0; p < parent; p++) { var getMethod = GetFirstMatchingMethod(ClassPropInfo.GetMembersForType(subTypes[p]).Where(m => m.CanGetMember && m.CanSetMember), subTypes[p + 1]); if (getMethod == null) { throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "In order to deserialize sub-objects, {0} must have a get/set method for type {1}", subTypes[p].FullName, subTypes[p + 1].FullName)); } getMethod.EmitGetValue(il); } break; } // call the deserializer for the subobject il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Call, deserializers[i]); // other than the root object, set the value on the parent object if (i == 0) { // store root object in loc.0 il.Emit(OpCodes.Stloc, localObject); } else { if (setMethod == null) { throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Cannot find set method for type {0} into type {1}", subTypes[i].FullName, subTypes[0].FullName)); } setMethod.EmitSetValue(il); } } // return the object from loc.0 il.Emit(OpCodes.Ldloc, localObject); il.Emit(OpCodes.Ret); // convert the dynamic method to a delegate var delegateType = typeof(Func <,>).MakeGenericType(typeof(IDataReader), type); return(dm.CreateDelegate(delegateType)); }
/// <summary> /// Create the deserializers for all of the sub-object types in the graph. /// </summary> /// <param name="subTypes">The list of sub-object types to parse.</param> /// <param name="reader">The reader to analyze.</param> /// <param name="structure">The structure of the record we are reading.</param> /// <param name="allowBindChild">True if the columns should be allowed to bind to children.</param> /// <returns>An array of delegates.</returns> private static DynamicMethod[] CreateDeserializersForSubObjects(Type[] subTypes, IDataReader reader, IRecordStructure structure, bool allowBindChild) { // take up to the first NoClass int subTypeCount = subTypes.Length; DynamicMethod[] deserializers = new DynamicMethod[subTypeCount]; int column = 0; for (int i = 0; i < subTypeCount; i++) { // determine the end of this type and the beginning of the next int endColumn = DetectEndColumn(reader, structure, column, subTypes, i); // generate a deserializer for the class var subType = subTypes[i]; if (TypeHelper.IsAtomicType(subType)) { deserializers[i] = CreateValueDeserializer(subType, column); } else { // sub-objects coming back through a LEFT JOIN may return a null for all columns. If so, we'll want to return a null object. // however if we're returning multiple recordsets and this is a sub-object of a guardian, we let the guardian figure out if the object is valid. var checkForAllDbNull = column > 0; if (i > 0) { var priorType = subTypes[i - 1]; if (priorType.GetTypeInfo().IsGenericType&& priorType.GetGenericTypeDefinition() == typeof(Guardian <,>)) { checkForAllDbNull = false; } } deserializers[i] = CreateClassDeserializerDynamicMethod(subType, reader, structure, column, endColumn - column, true, (i == 0), (i == 0) && allowBindChild, checkForAllDbNull); } column = endColumn; } return(deserializers); }
/// <summary> /// Maps the columns. /// </summary> /// <param name="type">The type being mapped.</param> /// <param name="reader">The reader being read.</param> /// <param name="startColumn">The start column index.</param> /// <param name="columnCount">The number of columns.</param> /// <param name="structure">The record structure, which may contain custom mapping.</param> /// <param name="allowBindChild">True if the context allows binding children (e.g. Merge Outputs)</param> /// <returns>The mapping.</returns> private static List <FieldMapping> MapColumns(Type type, IDataReader reader, int startColumn, int columnCount, IRecordStructure structure, bool allowBindChild = false) { // get the mapping from the reader to the type, excluding deep mappings var mapping = ColumnMapping.MapColumns(type, reader, startColumn, columnCount, structure); if (!allowBindChild) { for (int i = 0; i < mapping.Count; i++) { if (mapping[i] != null && mapping[i].IsDeep) { mapping[i] = null; } } } return(mapping); }
/// <summary> /// Create the deserializers for all of the sub-object types in the graph. /// </summary> /// <param name="subTypes">The list of sub-object types to parse.</param> /// <param name="reader">The reader to analyze.</param> /// <param name="structure">The structure of the record we are reading.</param> /// <returns>An array of delegates.</returns> private static DynamicMethod[] CreateDeserializersForSubObjects(Type[] subTypes, IDataReader reader, IRecordStructure structure) { // take up to the first NoClass int subTypeCount = subTypes.Length; DynamicMethod[] deserializers = new DynamicMethod[subTypeCount]; int column = 0; for (int i = 0; i < subTypeCount; i++) { // determine the end of this type and the beginning of the next int endColumn = DetectEndColumn(reader, structure, column, subTypes, i); // generate a deserializer for the class deserializers[i] = CreateClassDeserializerDynamicMethod(subTypes[i], reader, structure, column, endColumn - column, true, (i == 0)); column = endColumn; } return(deserializers); }
/// <summary> /// Get a deserializer to read class T from the given reader. /// </summary> /// <param name="reader">The reader to read from.</param> /// <param name="structure">The structure of the objects in the record.</param> /// <typeparam name="T">The type of object to deserialize.</typeparam> /// <returns>A function that can deserialize a T from the reader.</returns> public static Func <IDataReader, Action <object[]>, T> GetDeserializerWithCallback <T>(IDataReader reader, IRecordStructure structure) { return((Func <IDataReader, Action <object[]>, T>)GetDeserializer(reader, typeof(T), structure, SchemaMappingType.NewObjectWithCallback)); }
/// <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> /// <returns>If createNewObject=true, then Func<IDataReader, T>.</returns> private static Delegate CreateClassDeserializer(Type type, IDataReader reader, IRecordStructure structure, int startColumn, int columnCount, bool createNewObject) { var method = CreateClassDeserializerDynamicMethod(type, reader, structure, startColumn, columnCount, createNewObject, true, !createNewObject); // create a generic type for the delegate we are returning Type delegateType; if (createNewObject) { delegateType = typeof(Func <,>).MakeGenericType(typeof(IDataReader), type); } else { delegateType = typeof(Func <, ,>).MakeGenericType(typeof(IDataReader), type, type); } return(method.CreateDelegate(delegateType)); }
/// <summary> /// Detect the boundary between tOther and tNext in the reader. /// </summary> /// <param name="reader">The reader to analyze.</param> /// <param name="structure">The structure of the record we are reading.</param> /// <param name="columnIndex">The index of the next column to look at.</param> /// <param name="types">The list of types to be deserialized.</param> /// <param name="typeIndex">The index of the current type being deserialized.</param> /// <returns>The end boundary for the current object.</returns> private static int DetectEndColumn( IDataReader reader, IRecordStructure structure, int columnIndex, Type[] types, int typeIndex) { Type currentType = types[typeIndex]; Type nextType = (typeIndex + 1 < types.Length) ? types[typeIndex + 1] : typeof(object); // if the caller specified columns to split on then use that var splitColumns = structure.GetSplitColumns(); if (splitColumns != null) { // go through all of the remaining types for (int t = typeIndex + 1; t < types.Length; t++) { // get the column name for the id of the next type string columnName; if (!splitColumns.TryGetValue(types[t], out columnName)) continue; for (; columnIndex < reader.FieldCount; columnIndex++) { if (String.Compare(columnName, reader.GetName(columnIndex), StringComparison.OrdinalIgnoreCase) == 0) return columnIndex; } } } // get the setters for the class and the next class // for the current set, we want to simulate what we will actually use, so we only want to use unique matches // for the next set, we want to find all applicable matches, so we can detect the transition to the next object int fieldCount = reader.FieldCount; int columnsLeft = fieldCount - columnIndex; var currentSetters = ColumnMapping.Tables.CreateMapping(currentType, reader, null, null, structure, columnIndex, columnsLeft, true); // go through the remaining types to see if anything will claim the column int i = 0; for (; columnIndex + i < fieldCount; i++) { // if there is a setter for the current column, keep going if (currentSetters[i] != null) continue; // there isn't a setting for the column, so see if any other type can claim the column for (int t = typeIndex + 1; t < types.Length; t++) { // one of the next types can claim the column, so quit now var nextSetters = ColumnMapping.Tables.CreateMapping(types[t], reader, null, null, structure, columnIndex + i, 1, false); if (nextSetters[0] != null) return columnIndex + i; } } return columnIndex + i; }
/// <summary> /// Create the deserializers for all of the sub-object types in the graph. /// </summary> /// <param name="subTypes">The list of sub-object types to parse.</param> /// <param name="reader">The reader to analyze.</param> /// <param name="structure">The structure of the record we are reading.</param> /// <returns>An array of delegates.</returns> private static DynamicMethod[] CreateDeserializersForSubObjects(Type[] subTypes, IDataReader reader, IRecordStructure structure) { // take up to the first NoClass int subTypeCount = subTypes.Length; DynamicMethod[] deserializers = new DynamicMethod[subTypeCount]; int column = 0; for (int i = 0; i < subTypeCount; i++) { // determine the end of this type and the beginning of the next int endColumn = DetectEndColumn(reader, structure, column, subTypes, i); // generate a deserializer for the class deserializers[i] = CreateClassDeserializerDynamicMethod(subTypes[i], reader, structure, column, endColumn - column, true, (i == 0)); column = endColumn; } return deserializers; }
/// <summary> /// Creates a deserializer for a graph of objects. The objects are allocated to an array of objects and passed to a callback that assembles the objects. /// </summary> /// <param name="subTypes">The types of the subobjects.</param> /// <param name="reader">The reader to analyze.</param> /// <param name="structure">The structure of the record we are reading.</param> /// <returns>A function that takes an IDataReader and deserializes an object of type T.</returns> private static Delegate CreateGraphDeserializerWithCallback(Type[] subTypes, IDataReader reader, IRecordStructure structure) { Type type = subTypes[0]; // go through each of the subtypes var deserializers = CreateDeserializersForSubObjects(subTypes, reader, structure); // create a new anonymous method that takes an IDataReader and returns T var dm = new DynamicMethod( String.Format(CultureInfo.InvariantCulture, "Deserialize-{0}-{1}", type.FullName, Guid.NewGuid()), type, new[] { typeof(IDataReader), typeof(Action<object[]>) }, true); var il = dm.GetILGenerator(); // we have a callback function to call with our objects, so set up the delegate and the array var localObject = il.DeclareLocal(type); // store the result il.Emit(OpCodes.Ldarg_1); // push the delegate il.Emit(OpCodes.Ldc_I4, deserializers.Length); // create a new array il.Emit(OpCodes.Newarr, typeof(object)); /////////////////////////////////////////////////// // emit the method /////////////////////////////////////////////////// for (int i = 0; i < deserializers.Length; i++) { il.Emit(OpCodes.Dup); // duplicate the array il.Emit(OpCodes.Ldc_I4, i); // the index to store to // call the deserializer for the subobject il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Call, deserializers[i]); // for the root object, store it in our local variable if (i == 0) { il.Emit(OpCodes.Dup); il.Emit(OpCodes.Stloc, localObject); } il.Emit(OpCodes.Stelem, typeof(object)); // store the element in the array } // we have a callback function to call with our objects, so set up the delegate and the array il.Emit(OpCodes.Call, typeof(Action<object[]>).GetMethod("Invoke")); // invoke the delegate // return the result il.Emit(OpCodes.Ldloc, localObject); // put the root object back in for return il.Emit(OpCodes.Ret); // convert the dynamic method to a delegate var delegateType = typeof(Func<,,>).MakeGenericType(typeof(IDataReader), typeof(Action<object[]>), type); return dm.CreateDelegate(delegateType); }
/// <summary> /// Creates a deserializer for a graph of objects. /// </summary> /// <param name="subTypes">The types of the subobjects.</param> /// <param name="reader">The reader to analyze.</param> /// <param name="structure">The structure of the record we are reading.</param> /// <returns>A function that takes an IDataReader and deserializes an object of type T.</returns> private static Delegate CreateGraphDeserializer(Type[] subTypes, IDataReader reader, IRecordStructure structure) { Type type = subTypes[0]; bool isStruct = type.IsValueType; // go through each of the subtypes var deserializers = CreateDeserializersForSubObjects(subTypes, reader, structure); // create a new anonymous method that takes an IDataReader and returns T var dm = new DynamicMethod( String.Format(CultureInfo.InvariantCulture, "Deserialize-{0}-{1}", type.FullName, Guid.NewGuid()), type, new[] { typeof(IDataReader) }, true); var il = dm.GetILGenerator(); var localObject = il.DeclareLocal(type); // keep track of the properties that we have already used // the tuple is the level + property var usedMethods = new HashSet<Tuple<int, ClassPropInfo>>(); /////////////////////////////////////////////////// // emit the method /////////////////////////////////////////////////// for (int i = 0; i < deserializers.Length; i++) { // if there is no deserializer for this object, then skip it if (deserializers[i] == null) continue; // for subobjects, dup the core object so we can set values on it if (i > 0) { if (isStruct) il.Emit(OpCodes.Ldloca_S, localObject); else il.Emit(OpCodes.Ldloc, localObject); } // if we don't have a callback, then we are going to store the value directly into the field on T or one of the subobjects // here we determine the proper set method to store into. // we are going to look into all of the types in the graph and find the first parameter that matches our current type ClassPropInfo setMethod = null; for (int parent = 0; parent < i; parent++) { // find the set method on the current parent setMethod = GetFirstMatchingMethod(ClassPropInfo.GetMembersForType(subTypes[parent]).Where(m => m.CanSetMember), subTypes[i]); // make sure that at a given level, we only use the method once var tuple = Tuple.Create(parent, setMethod); if (usedMethods.Contains(tuple)) continue; else usedMethods.Add(tuple); // if we didn't find a matching set method, then continue on to the next type in the graph if (setMethod == null) continue; // if the parent is not the root object, we have to drill down to the parent, then set the value // the root object is already on the stack, so emit a get method to get the object to drill down into for (int p = 0; p < parent; p++) { var getMethod = GetFirstMatchingMethod(ClassPropInfo.GetMembersForType(subTypes[p]).Where(m => m.CanGetMember && m.CanSetMember), subTypes[p + 1]); if (getMethod == null) throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "In order to deserialize sub-objects, {0} must have a get/set method for type {1}", subTypes[p].FullName, subTypes[p + 1].FullName)); getMethod.EmitGetValue(il); } break; } // call the deserializer for the subobject il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Call, deserializers[i]); // other than the root object, set the value on the parent object if (i == 0) { // store root object in loc.0 il.Emit(OpCodes.Stloc, localObject); } else { if (setMethod == null) throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Cannot find set method for type {0} into type {1}", subTypes[i].FullName, subTypes[0].FullName)); setMethod.EmitSetValue(il); } } // return the object from loc.0 il.Emit(OpCodes.Ldloc, localObject); il.Emit(OpCodes.Ret); // convert the dynamic method to a delegate var delegateType = typeof(Func<,>).MakeGenericType(typeof(IDataReader), type); return dm.CreateDelegate(delegateType); }
/// <summary> /// Creates the list of property setters for a reader. /// </summary> /// <param name="type">The type of object to map to.</param> /// <param name="reader">The reader to read.</param> /// <param name="command">The command that is currently being mapped.</param> /// <param name="parameters">The list of parameters used in the mapping operation.</param> /// <param name="structure">The structure of the record being read.</param> /// <param name="startColumn">The index of the first column to map.</param> /// <param name="columnCount">The number of columns to map.</param> /// <param name="uniqueMatches">True to only return the first match per field, false to return all matches per field.</param> /// <returns>An array of setters.</returns> internal ColumnMappingEventArgs[] CreateMapping( Type type, IDataReader reader, IDbCommand command, IList <IDataParameter> parameters, IRecordStructure structure, int startColumn, int columnCount, bool uniqueMatches) { ColumnMappingEventArgs[] mapping = new ColumnMappingEventArgs[columnCount]; // convert the list of names into a list of set reflections // clone the methods list, since we are only going to use each setter once (i.e. if you return two ID columns, we will only use the first one) // Also, we want to do a case-insensitive lookup of the property, so convert the dictionary to an uppercase dictionary var setMethods = new Dictionary <string, ClassPropInfo>(ClassPropInfo.GetMappingForType(type), StringComparer.OrdinalIgnoreCase); List <IDataParameter> readOnlyParameters = null; if (parameters != null) { readOnlyParameters = new List <IDataParameter>(parameters.OfType <IDataParameter>().ToList()); } // find all of the mappings for (int i = 0; i < columnCount; i++) { // generate an event var e = new ColumnMappingEventArgs() { TargetType = type, Reader = reader, FieldIndex = i + startColumn, Parameters = readOnlyParameters, }; if (command != null) { e.CommandText = command.CommandText; e.CommandType = command.CommandType; } if (reader != null) { e.ColumnName = reader.GetSchemaTable().Rows[e.FieldIndex]["ColumnName"].ToString(); } lock (_lock) { _mappings(null, e); } // if no mapping was returned, then skip the column if (e.Canceled || String.IsNullOrEmpty(e.TargetFieldName.Trim())) { continue; } // if a column mapping override was specified, then attempt an override if (structure != null) { structure.MapColumn(e); } // get the target property based on the result string targetFieldName = e.TargetFieldName; // first see if there is a wildcard column, if not, then look up the field name ClassPropInfo setter; if (setMethods.TryGetValue("*", out setter) || setMethods.TryGetValue(targetFieldName, out setter)) { mapping[i] = e; e.ClassPropInfo = setter; InitializeMappingSerializer(e, reader, command, parameters, i); // remove the name from the list so we can only use it once if (uniqueMatches) { setMethods.Remove(setter.ColumnName); } } } return(mapping); }
/// <summary> /// Get a deserializer to read class T from the given reader. /// </summary> /// <param name="reader">The reader to read from.</param> /// <param name="structure">The structure of the objects in the record.</param> /// <typeparam name="T">The type of object to deserialize.</typeparam> /// <returns>A function that can deserialize a T from the reader.</returns> public static Func <IDataReader, T> GetDeserializer <T>(IDataReader reader, IRecordStructure structure) { return((Func <IDataReader, T>)GetDeserializer(reader, typeof(T), structure, SchemaMappingType.NewObject)); }
/// <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; }
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); }
/// <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); }