/// <summary> /// Searches the type and child types for a field that matches the given column name. /// </summary> /// <param name="type">The type to search.</param> /// <param name="columnName">The column to search for.</param> /// <param name="mappingCollection">The mapping collection containing the configuration for this context.</param> /// <returns>The name of the field or null if there is no match.</returns> internal static string SearchForMatchingField(Type type, string columnName, MappingCollection mappingCollection) { var queue = new Queue <Tuple <Type, string> >(); queue.Enqueue(Tuple.Create(type, String.Empty)); var searched = new HashSet <Type>(); while (queue.Any()) { var tuple = queue.Dequeue(); var t = tuple.Item1; var prefix = tuple.Item2; searched.Add(t); var prop = GetMemberByColumnName(t, columnName); if (prop != null) { return(prefix + prop.Name); } if (mappingCollection.CanBindChild(t)) { foreach (var p in ClassPropInfo.GetMembersForType(t).Where(m => !TypeHelper.IsAtomicType(m.MemberType))) { if (!searched.Contains(p.MemberType)) { queue.Enqueue(Tuple.Create(p.MemberType, prefix + p.Name + MemberSeparator)); } } } } return(null); }
/// <summary> /// Initializes a new instance of the FieldMapping class. /// </summary> /// <param name="pathToMember">The path to the member.</param> /// <param name="member">The member that is bound.</param> /// <param name="serializer">The serializer for the mapping.</param> public FieldMapping(string pathToMember, ClassPropInfo member, IDbObjectSerializer serializer) { PathToMember = pathToMember; Member = member; Serializer = serializer; Prefix = ClassPropInfo.GetMemberPrefix(pathToMember); IsDeep = (Prefix != null); }
/// <summary> /// Emits the code to get a value from a type, allowing dot accessors to select children. /// </summary> /// <param name="type">The base type.</param> /// <param name="memberName">The name of the member in the form a.b.c.d.</param> /// <param name="il">The ILGenerator to use,</param> /// <param name="readyToSetLabel">The label to jump to if any of the non-terminal accesses result (a.b.c) in null.</param> internal static void EmitGetValue(Type type, string memberName, ILGenerator il, Label?readyToSetLabel = null) { ClassPropInfo pi = null; var memberNames = SplitMemberName(memberName).ToArray(); Label nullLabel = il.DefineLabel(); // emit the code to get the value for (int i = 0; i < memberNames.Length; i++) { var member = memberNames[i]; // if the value on the stack can be null, do a null check here if (pi != null && !pi.MemberType.GetTypeInfo().IsValueType) { il.Emit(OpCodes.Dup); il.Emit(OpCodes.Brfalse, nullLabel); } pi = FindMember(type, member); if (pi == null || !pi.CanGetMember) { throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Get method {0} not found on {1}", member, type)); } pi.EmitGetValue(il); type = pi.MemberType; } // emit the code to handle nulls var memberType = pi.MemberType; if (memberNames.Length > 1) { var doneLabel = il.DefineLabel(); il.Emit(OpCodes.Br, doneLabel); // if any of the prefix values are null, then we emit the default of the final type il.MarkLabel(nullLabel); il.Emit(OpCodes.Pop); il.Emit(OpCodes.Ldnull); if (readyToSetLabel != null) { il.Emit(OpCodes.Br, readyToSetLabel.Value); } il.MarkLabel(doneLabel); } // 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 (memberType.GetTypeInfo().IsValueType) { il.Emit(OpCodes.Box, memberType); } }
/// <summary> /// Splits a member name on dots and returns the prefix to the member. /// </summary> /// <param name="memberName">The name of the member.</param> /// <returns>The prefix.</returns> internal static string GetMemberPrefix(string memberName) { var split = ClassPropInfo.SplitMemberName(memberName); if (split.Count() < 2) { return(null); } return(String.Join(ClassPropInfo.MemberSeparator, split.Take(split.Count() - 1).ToArray())); }
/// <summary> /// Gets the member from a type, allowing dot accessors to select children. /// </summary> /// <param name="type">The type to search.</param> /// <param name="memberName">The name of the member in the form a.b.c.d.</param> /// <returns>The member or null if not found.</returns> internal static ClassPropInfo FindMember(Type type, string memberName) { ClassPropInfo pi = null; foreach (var member in SplitMemberName(memberName)) { pi = GetMemberByName(type, member); if (pi == null) { return(null); } type = pi.MemberType; } return(pi); }
/// <summary> /// Uses IL to generate a method that converts an object of a given type to a FastExpando. /// </summary> /// <param name="type">The type of object to be able to convert.</param> /// <returns>A function that can convert that type of object to a FastExpando.</returns> private static Func <object, FastExpando> CreateConverter(Type type) { // create a dynamic method var dm = new DynamicMethod(String.Format(CultureInfo.InvariantCulture, "ExpandoGenerator-{0}", type.FullName), typeof(FastExpando), new[] { typeof(object) }, typeof(ExpandoGenerator), true); var il = dm.GetILGenerator(); var source = il.DeclareLocal(type); // load the parameter object onto the stack and convert it into the local variable il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Castclass, type); il.Emit(OpCodes.Stloc, source); // new instance of fastexpando // top of stack il.Emit(OpCodes.Newobj, _constructor); // for each public field or method, get the value foreach (ClassPropInfo accessor in ClassPropInfo.GetMembersForType(type).Where(m => m.CanGetMember)) { il.Emit(OpCodes.Dup); // push expando - so we can call set value il.Emit(OpCodes.Ldstr, accessor.Name); // push name // get the value of the field or property il.Emit(OpCodes.Ldloc, source); accessor.EmitGetValue(il); // value types need to be boxed if (accessor.MemberType.GetTypeInfo().IsValueType) { il.Emit(OpCodes.Box, accessor.MemberType); } // call expando.setvalue il.Emit(OpCodes.Callvirt, _fastExpandoSetValue); } // return expando - it should be left on the stack il.Emit(OpCodes.Ret); return((Func <object, FastExpando>)dm.CreateDelegate(typeof(Func <object, FastExpando>))); }
/// <summary> /// Generates an accessor for a property in the object. /// </summary> /// <param name="type">The type of object being read.</param> /// <param name="mapping">The column mapping.</param> /// <param name="columnInfo">The column information.</param> /// <returns>An accessor function or null if there is no mapping.</returns> private static Func <object, object> GenerateAccessor(Type type, FieldMapping mapping, ColumnInfo columnInfo) { if (mapping == null) { return(null); } ClassPropInfo propInfo = mapping.Member; // create a new anonymous method that takes an object and returns the value var dm = new DynamicMethod(string.Format(CultureInfo.InvariantCulture, "GetValue-{0}-{1}", type.FullName, Guid.NewGuid()), typeof(object), new[] { typeof(object) }, true); var il = dm.GetILGenerator(); // convert the object reference to the desired type il.Emit(OpCodes.Ldarg_0); if (type.IsValueType) { // access the field/property of a value type var valueHolder = il.DeclareLocal(type); il.Emit(OpCodes.Unbox_Any, type); il.Emit(OpCodes.Stloc, valueHolder); il.Emit(OpCodes.Ldloca_S, valueHolder); } else { il.Emit(OpCodes.Isinst, type); // cast object -> type } // get the value from the object var readyToSetLabel = il.DefineLabel(); ClassPropInfo.EmitGetValue(type, mapping.PathToMember, il, readyToSetLabel); if (mapping.Member.MemberType.IsValueType) { il.Emit(OpCodes.Unbox_Any, mapping.Member.MemberType); } // if the type is nullable, handle nulls Type sourceType = propInfo.MemberType; Type targetType = columnInfo.ColumnType; Type underlyingType = Nullable.GetUnderlyingType(sourceType); if (underlyingType != null) { // check for not null Label notNullLabel = il.DefineLabel(); var nullableHolder = il.DeclareLocal(propInfo.MemberType); il.Emit(OpCodes.Stloc, nullableHolder); il.Emit(OpCodes.Ldloca_S, nullableHolder); il.Emit(OpCodes.Call, sourceType.GetProperty("HasValue").GetGetMethod()); il.Emit(OpCodes.Brtrue_S, notNullLabel); // it's null, just return null il.Emit(OpCodes.Ldnull); il.Emit(OpCodes.Ret); il.MarkLabel(notNullLabel); // it's not null, so unbox to the underlyingtype il.Emit(OpCodes.Ldloca, nullableHolder); il.Emit(OpCodes.Call, sourceType.GetProperty("Value").GetGetMethod()); // at this point we have de-nulled value, so use those converters sourceType = underlyingType; } // if the serializer isn't the default ToStringSerializer, and it can serialize the type properly, then use it // otherwise we'll use coersion or conversion var targetDbType = DbParameterGenerator.LookupDbType(targetType); bool canSerialize = sourceType != typeof(string) && mapping.Serializer.GetType() != typeof(ToStringObjectSerializer) && mapping.Serializer.CanSerialize(sourceType, targetDbType); if (sourceType != targetType && canSerialize) { if (sourceType.IsValueType) { il.Emit(OpCodes.Box, sourceType); } il.EmitLoadType(sourceType); StaticFieldStorage.EmitLoad(il, mapping.Serializer); il.Emit(OpCodes.Call, typeof(ObjectReader).GetMethod("SerializeObject", BindingFlags.NonPublic | BindingFlags.Static)); } else { // attempt to convert the value // either way, we are putting it in an object variable, so box it if (TypeConverterGenerator.EmitConversionOrCoersion(il, sourceType, targetType)) { il.Emit(OpCodes.Box, targetType); } else { il.Emit(OpCodes.Box, sourceType); } } il.MarkLabel(readyToSetLabel); il.Emit(OpCodes.Ret); return((Func <object, object>)dm.CreateDelegate(typeof(Func <object, object>))); }
private ObjectReader(IDbCommand command, Type type, IDataReader reader) { var provider = InsightDbProvider.For(command); // copy the schema and fix it SchemaTable = reader.GetSchemaTable().Copy(); FixupSchemaNumericScale(); FixupSchemaRemoveReadOnlyColumns(); IsAtomicType = TypeHelper.IsAtomicType(type); if (!IsAtomicType) { // create a mapping, and only keep mappings that match our modified schema var mappings = ColumnMapping.Tables.CreateMapping(type, reader, null, null, null, 0, reader.FieldCount, true) .Where(m => m != null).ToArray(); int columnCount = SchemaTable.Rows.Count; _accessors = new Func <object, object> [columnCount]; _memberTypes = new Type[columnCount]; for (int i = 0; i < columnCount; i++) { var columnName = SchemaTable.Rows[i]["ColumnName"].ToString(); var mapping = mappings.FirstOrDefault(m => String.Compare(m.ColumnName, columnName, StringComparison.OrdinalIgnoreCase) == 0); if (mapping == null) { continue; } ClassPropInfo propInfo = mapping.ClassPropInfo; // create a new anonymous method that takes an object and returns the value var dm = new DynamicMethod(string.Format(CultureInfo.InvariantCulture, "GetValue-{0}-{1}", type.FullName, Guid.NewGuid()), typeof(object), new[] { typeof(object) }, true); var il = dm.GetILGenerator(); // convert the object reference to the desired type il.Emit(OpCodes.Ldarg_0); if (type.IsValueType) { // access the field/property of a value type var valueHolder = il.DeclareLocal(type); il.Emit(OpCodes.Unbox_Any, type); il.Emit(OpCodes.Stloc, valueHolder); il.Emit(OpCodes.Ldloca_S, valueHolder); } else { il.Emit(OpCodes.Isinst, type); // cast object -> type } // get the value from the object propInfo.EmitGetValue(il); // if the type is nullable, handle nulls Type sourceType = propInfo.MemberType; Type targetType = (Type)SchemaTable.Rows[i]["DataType"]; Type underlyingType = Nullable.GetUnderlyingType(sourceType); if (underlyingType != null) { // check for not null Label notNullLabel = il.DefineLabel(); var nullableHolder = il.DeclareLocal(propInfo.MemberType); il.Emit(OpCodes.Stloc, nullableHolder); il.Emit(OpCodes.Ldloca_S, nullableHolder); il.Emit(OpCodes.Call, sourceType.GetProperty("HasValue").GetGetMethod()); il.Emit(OpCodes.Brtrue_S, notNullLabel); // it's null, just return null il.Emit(OpCodes.Ldnull); il.Emit(OpCodes.Ret); il.MarkLabel(notNullLabel); // it's not null, so unbox to the underlyingtype il.Emit(OpCodes.Ldloca, nullableHolder); il.Emit(OpCodes.Call, sourceType.GetProperty("Value").GetGetMethod()); // at this point we have de-nulled value, so use those converters sourceType = underlyingType; } if (sourceType != targetType && !sourceType.IsValueType && sourceType != typeof(string)) { // if the provider type is Xml, then serialize the value 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(sourceType); il.Emit(OpCodes.Call, serializerMethod); } else { // attempt to convert the value // either way, we are putting it in an object variable, so box it if (TypeConverterGenerator.EmitConversionOrCoersion(il, sourceType, targetType)) { il.Emit(OpCodes.Box, targetType); } else { il.Emit(OpCodes.Box, sourceType); } } il.Emit(OpCodes.Ret); _memberTypes[i] = propInfo.MemberType; _accessors[i] = (Func <object, object>)dm.CreateDelegate(typeof(Func <object, object>)); } } else { // we are working off a single-column atomic type _memberTypes = new Type[1] { type }; _accessors = new Func <object, object>[] { o => o }; } }
/// <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)); }
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> /// 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.MapParameters(type, command, parameters); for (int i = 0; i < mappings.Count; i++) { var finishLabel = il.DefineLabel(); // 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.Member; 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); // if this is a deep mapping, then get the parent object, and do a null test if its not a value type if (mapping.IsDeep) { ClassPropInfo.EmitGetValue(type, mapping.Prefix, il); if (!ClassPropInfo.FindMember(type, mapping.Prefix).MemberType.GetTypeInfo().IsValueType) { il.Emit(OpCodes.Dup); var label = il.DefineLabel(); il.Emit(OpCodes.Brtrue, label); il.Emit(OpCodes.Pop); // pop the object before finishing il.Emit(OpCodes.Br, finishLabel); il.MarkLabel(label); } } // 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 TypeConverterGenerator.EmitConvertAndSetValue(il, _dbTypeToTypeMap[parameter.DbType], mapping); il.MarkLabel(finishLabel); } il.Emit(OpCodes.Ret); return((Action <IDbCommand, object>)dm.CreateDelegate(typeof(Action <IDbCommand, object>))); }
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>))); }
/// <summary> /// Emit the IL to convert the current value on the stack and set the value of the object. /// </summary> /// <param name="il">The IL generator to output to.</param> /// <param name="sourceType">The current type of the value.</param> /// <param name="method">The set property method to call.</param> /// <remarks> /// Expects the stack to contain: /// Target Object /// Value to set /// The value is first converted to the type required by the method parameter, then sets the property. /// </remarks> /// <returns>A label that needs to be marked at the end of a succesful set.</returns> public static Label EmitConvertAndSetValue(ILGenerator il, Type sourceType, ClassPropInfo method) { // targetType - the target type we need to convert to // underlyingTargetType - if the target type is nullable, we need to look at the underlying target type // rawTargetType - if the underlying target type is enum, we need to look at the underlying target type for that // sourceType - this is the type of the data in the data set Type targetType = method.MemberType; Type underlyingTargetType = Nullable.GetUnderlyingType(targetType) ?? targetType; // some labels that we need Label isDbNullLabel = il.DefineLabel(); Label finishLabel = il.DefineLabel(); // if the value is DbNull, then we continue to the next item il.Emit(OpCodes.Dup); // dup value, stack => [target][value][value] il.Emit(OpCodes.Isinst, typeof(DBNull)); // isinst DBNull:value, stack => [target][value-as-object][DBNull or null] il.Emit(OpCodes.Brtrue_S, isDbNullLabel); // br.true isDBNull, stack => [target][value-as-object] // handle the special target types first if (targetType == typeof(char)) { // char il.EmitCall(OpCodes.Call, _readChar, null); } else if (targetType == typeof(char?)) { // char? il.EmitCall(OpCodes.Call, _readNullableChar, null); } else if (targetType == typeof(System.Data.Linq.Binary)) { // unbox sql byte arrays to Linq.Binary // before: stack => [target][object-value] // after: stack => [target][byte-array-value] il.Emit(OpCodes.Unbox_Any, typeof(byte[])); // stack is now [target][byte-array] // before: stack => [target][byte-array-value] // after: stack => [target][Linq.Binary-value] il.Emit(OpCodes.Newobj, _linqBinaryCtor); } else if (targetType == typeof(XmlDocument)) { // special handler for XmlDocuments // before: stack => [target][object-value] il.Emit(OpCodes.Call, _readXmlDocument); // after: stack => [target][xmlDocument] } else if (targetType == typeof(XDocument)) { // special handler for XDocuments // before: stack => [target][object-value] il.Emit(OpCodes.Call, _readXDocument); // after: stack => [target][xDocument] } else if (sourceType == typeof(string) && targetType != typeof(string) && !targetType.IsValueType) { // we are getting a string from the database, but the target is not a string, but it's a reference type // assume the column is an xml data type and that we want to deserialize it // before: stack => [target][object-value] il.EmitLoadType(targetType); // after: stack => [target][object-value][memberType] il.Emit(OpCodes.Call, _deserializeXml); il.Emit(OpCodes.Unbox_Any, targetType); } else if (underlyingTargetType.IsEnum && sourceType == typeof(string)) { var localString = il.DeclareLocal(typeof(string)); // if we are converting a string to an enum, then parse it. // see if the value from the database is a string. if so, we need to parse it. If not, we will just try to unbox it. il.Emit(OpCodes.Isinst, typeof(string)); // is string, stack => [target][string] il.Emit(OpCodes.Stloc, localString); // pop loc.2 (enum), stack => [target] // call enum.parse (type, value, true) il.EmitLoadType(underlyingTargetType); il.Emit(OpCodes.Ldloc, localString); // push enum, stack => [target][enum-type][string] il.Emit(OpCodes.Ldc_I4_1); // push true, stack => [target][enum-type][string][true] il.EmitCall(OpCodes.Call, _enumParse, null); // call Enum.Parse, stack => [target][enum-as-object] // Enum.Parse returns an object, which we need to unbox to the enum value il.Emit(OpCodes.Unbox_Any, underlyingTargetType); } else { // this isn't a system value type, so unbox to the type the reader is giving us (this is a system type, hopefully) // now we have an unboxed sourceType il.Emit(OpCodes.Unbox_Any, sourceType); // look for a constructor that takes the type as a parameter Type[] sourceTypes = new Type[] { sourceType }; ConstructorInfo ci = targetType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, sourceTypes, null); if (ci != null) { // if the constructor only takes nullable types, warn the programmer if (Nullable.GetUnderlyingType(ci.GetParameters()[0].ParameterType) != null) throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Class {0} must provide a constructor taking a parameter of type {1}. Nullable parameters are not supported.", targetType, sourceType)); il.Emit(OpCodes.Newobj, ci); } else { // attempt to convert the value to the target type if (!EmitConversionOrCoersion(il, sourceType, targetType)) { if (sourceType != targetType) { throw new InvalidOperationException(String.Format( CultureInfo.InvariantCulture, "Field {0} cannot be converted from {1} to {2}. Create a conversion constructor or conversion operator.", method.Name, sourceType, targetType)); } } // if the target is nullable, then construct the nullable from the data if (Nullable.GetUnderlyingType(targetType) != null) il.Emit(OpCodes.Newobj, targetType.GetConstructor(new[] { underlyingTargetType })); } } ///////////////////////////////////////////////////////////////////// // now the stack has [target][value-unboxed]. we can set the value now method.EmitSetValue(il); // stack is now EMPTY ///////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// // jump over our DBNull handler il.Emit(OpCodes.Br_S, finishLabel); ///////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////// // cleanup after IsDBNull. ///////////////////////////////////////////////////////////////////// il.MarkLabel(isDbNullLabel); // stack => [target][value] il.Emit(OpCodes.Pop); // pop value, stack => [target] // if the type is an object, set the value to null // this is necessary for overwriting output parameters, // as well as overwriting any properties that may be set in the constructor of the object if (!method.MemberType.IsValueType) { il.Emit(OpCodes.Ldnull); // push null method.EmitSetValue(il); } else { // we didn't call setvalue, so pop the target object off the stack il.Emit(OpCodes.Pop); // pop target, stack => [empty] } return finishLabel; }
/// <summary> /// Create accessors to pull data from the object of the given type for the given schema. /// </summary> /// <param name="type">The type to analyze.</param> /// <param name="identity">The schema identity to analyze.</param> /// <returns>A list of accessor functions to get values from the type.</returns> private FieldReaderData CreateFieldReaderData(Type type, SchemaIdentity identity) { FieldReaderData readerData = new FieldReaderData(); readerData.Accessors = new List<Func<object, object>>(); readerData.MemberTypes = new List<Type>(); for (int i = 0; i < identity.SchemaTable.Rows.Count; i++) { // get the name of the column string name = _schemaTable.Rows[i]["ColumnName"].ToString(); // create a new anonymous method that takes an object and returns the value var dm = new DynamicMethod(string.Format(CultureInfo.InvariantCulture, "GetValue-{0}-{1}", type.FullName, Guid.NewGuid()), typeof(object), new[] { typeof(object) }, true); var il = dm.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); // push object argument il.Emit(OpCodes.Isinst, type); // cast object -> type // get the value from the object ClassPropInfo propInfo = new ClassPropInfo(type, name); propInfo.EmitGetValue(il); propInfo.EmitBox(il); il.Emit(OpCodes.Ret); readerData.MemberTypes.Add(propInfo.MemberType); readerData.Accessors.Add((Func<object, object>)dm.CreateDelegate(typeof(Func<object, object>))); } return readerData; }