Example #1
0
        /// <summary>
        /// Create a deserializer that just reads a value.
        /// </summary>
        /// <param name="type">The type to read.</param>
        /// <param name="column">The index of column to read.</param>
        /// <returns>A method that reads the value.</returns>
        private static DynamicMethod CreateValueDeserializer(Type type, int column)
        {
            var dm = new DynamicMethod(
                String.Format(CultureInfo.InvariantCulture, "Deserialize-{0}-{1}", type.FullName, Guid.NewGuid()),
                type,
                new[] { typeof(IDataReader) },
                true);

            // get the il generator and put some local variables on the stack
            var il = dm.GetILGenerator();

            // need to call IDataReader.GetItem to get the value of the field
            il.Emit(OpCodes.Ldarg_0);                                           // push arg.0 (reader), stack => [target][reader]
            IlHelper.EmitLdInt32(il, column);                                   // push index, stack => [target][reader][index]
            il.Emit(OpCodes.Callvirt, _iDataReaderGetItem);                     // call getItem, stack => [target][value-as-object]

            il.Emit(OpCodes.Call, typeof(ClassDeserializerGenerator).GetMethod("DBValueToT", BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod(type));
            il.Emit(OpCodes.Ret);

            return(dm);
        }
        private static void EmitMethodImpl(ModuleBuilder mb, TypeBuilder tb, MethodInfo interfaceMethod, FieldBuilder connectionField)
        {
            // look at the parameters on the interface
            var parameters     = interfaceMethod.GetParameters();
            var parameterTypes = parameters.Select(p => p.ParameterType).ToArray();

            // determine the proper method to call
            MethodInfo executeMethod = GetExecuteMethod(interfaceMethod);

            // get the sql attributes from the  method and class/interface
            var sqlAttribute     = interfaceMethod.GetCustomAttributes(false).OfType <SqlAttribute>().FirstOrDefault() ?? new SqlAttribute();
            var typeSqlAttribute = interfaceMethod.DeclaringType.GetCustomAttributes(false).OfType <SqlAttribute>().FirstOrDefault() ?? new SqlAttribute();

            // calculate the query parameters
            var schema      = sqlAttribute.Schema ?? typeSqlAttribute.Schema;
            var procName    = (executeMethod.DeclaringType == typeof(DBConnectionExtensions)) ? Regex.Replace(interfaceMethod.Name, "Async$", String.Empty, RegexOptions.IgnoreCase) : interfaceMethod.Name;
            var sql         = sqlAttribute.Sql ?? typeSqlAttribute.Sql ?? procName;
            var commandType = sqlAttribute.CommandType ?? typeSqlAttribute.CommandType ?? (sql.Contains(' ') ? CommandType.Text : CommandType.StoredProcedure);

            if (commandType == CommandType.StoredProcedure && !schema.IsNullOrWhiteSpace() && !sql.Contains('.'))
            {
                sql = schema.Trim() + "." + sql;
            }

            // start a new method
            MethodBuilder m = tb.DefineMethod(interfaceMethod.Name, MethodAttributes.Public | MethodAttributes.Virtual);

            TypeHelper.CopyMethodSignature(interfaceMethod, m);
            ILGenerator mIL = m.GetILGenerator();

            LocalBuilder parameterWrapper = null;

            var executeParameters = executeMethod.GetParameters();

            for (int i = 0; i < executeParameters.Length; i++)
            {
                switch (executeParameters[i].Name)
                {
                case "connection":
                    // get the connection from the getConnection method
                    mIL.Emit(OpCodes.Ldarg_0);
                    mIL.Emit(OpCodes.Ldfld, connectionField);
                    if (connectionField.FieldType == typeof(Func <IDbConnection>))
                    {
                        mIL.Emit(OpCodes.Call, connectionField.FieldType.GetMethod("Invoke"));
                    }
                    break;

                case "sql":
                    // if the sql attribute is on the method, use that
                    mIL.Emit(OpCodes.Ldstr, sql);
                    break;

                case "parameters":
                    // load all of the parameters and convert it to a parameters object
                    if (parameters.Length == 0)
                    {
                        // no parameters, just pass null
                        mIL.Emit(OpCodes.Ldnull);
                    }
                    else if (parameters.Length == 1 && !TypeHelper.IsAtomicType(parameters[0].ParameterType))
                    {
                        // one parameter that is a non-atomic object, just pass it and let the insight framework handle it
                        mIL.Emit(OpCodes.Ldarg_1);
                        if (parameters[0].ParameterType.IsValueType)
                        {
                            mIL.Emit(OpCodes.Box, parameters[0].ParameterType);
                        }
                    }
                    else
                    {
                        // create a class for the parameters and stick them in there
                        Type parameterWrapperType = CreateParameterClass(mb, parameters);
                        for (int pi = 0; pi < parameters.Length; pi++)
                        {
                            mIL.Emit(OpCodes.Ldarg, pi + 1);
                        }
                        mIL.Emit(OpCodes.Newobj, parameterWrapperType.GetConstructors()[0]);

                        // store the parameters in a local so we can unwrap output parameters
                        parameterWrapper = mIL.DeclareLocal(parameterWrapperType);
                        mIL.Emit(OpCodes.Dup);
                        mIL.Emit(OpCodes.Stloc, parameterWrapper);
                    }

                    break;

                case "outputParameters":
                    // fill in the output parameters object with the temporary parameters object
                    if (parameterWrapper != null)
                    {
                        mIL.Emit(OpCodes.Ldloc, parameterWrapper.LocalIndex);
                    }
                    else
                    {
                        mIL.Emit(OpCodes.Ldnull);
                    }
                    break;

                case "inserted":
                    // always pass argument 1 in
                    mIL.Emit(OpCodes.Ldarg_1);
                    break;

                case "returns":
                    if (!EmitSpecialParameter(mIL, "returns", parameters, executeParameters))
                    {
                        GenerateReturnsStructure(interfaceMethod, mb, mIL);
                    }
                    break;

                case "commandType":
                    if (EmitSpecialParameter(mIL, "commandType", parameters, executeParameters))
                    {
                        break;
                    }

                    IlHelper.EmitLdInt32(mIL, (int)commandType);
                    break;

                case "commandBehavior":
                    if (EmitSpecialParameter(mIL, "commandBehavior", parameters, executeParameters))
                    {
                        break;
                    }

                    IlHelper.EmitLdInt32(mIL, (int)CommandBehavior.Default);
                    break;

                case "closeConnection":
                    if (EmitSpecialParameter(mIL, "closeConnection", parameters, executeParameters))
                    {
                        break;
                    }

                    IlHelper.EmitLdInt32(mIL, (int)0);
                    break;

                case "commandTimeout":
                    if (EmitSpecialParameter(mIL, "commandTimeout", parameters, executeParameters))
                    {
                        break;
                    }

                    var commandTimeout = mIL.DeclareLocal(typeof(int?));
                    mIL.Emit(OpCodes.Ldloca_S, commandTimeout);
                    mIL.Emit(OpCodes.Initobj, typeof(int?));
                    mIL.Emit(OpCodes.Ldloc, commandTimeout);
                    break;

                case "transaction":
                    if (EmitSpecialParameter(mIL, "transaction", parameters, executeParameters))
                    {
                        break;
                    }

                    mIL.Emit(OpCodes.Ldnull);
                    break;

                case "cancellationToken":
                    if (EmitSpecialParameter(mIL, "cancellationToken", parameters, executeParameters))
                    {
                        break;
                    }

                    var cancellationToken = mIL.DeclareLocal(typeof(CancellationToken?));
                    mIL.Emit(OpCodes.Ldloca_S, cancellationToken);
                    mIL.Emit(OpCodes.Initobj, typeof(CancellationToken?));
                    mIL.Emit(OpCodes.Ldloc, cancellationToken);
                    break;

                default:
                    throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Cannot determine how to generate parameter {1} for method {0}", executeMethod.Name, executeParameters[i].Name));
                }
            }

            // call the execute method
            mIL.Emit(OpCodes.Call, executeMethod);

            // if the method returns void, throw away the return value
            if (interfaceMethod.ReturnType == typeof(void))
            {
                mIL.Emit(OpCodes.Pop);
            }

            // copy the output parameters from our parameter structure back to the output parameters
            EmitOutputParameters(parameters, parameterWrapper, mIL);

            mIL.Emit(OpCodes.Ret);
        }
        static Action <IDbCommand, object> CreateClassInputParameterGenerator(IDbCommand command, Type type)
        {
            var provider   = InsightDbProvider.For(command);
            var parameters = provider.DeriveParameters(command);

            // special case if the parameters object is an IEnumerable or Array
            // look for the parameter that is a Structured object and pass the array to the TVP
            // note that string supports ienumerable, so exclude atomic types
            var enumerable = type.GetInterfaces().FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable <>));

            if (enumerable != null && type != typeof(string) && parameters.OfType <IDataParameter>().Where(p => p.Direction.HasFlag(ParameterDirection.Input)).Count() == 1)
            {
                return((IDbCommand cmd, object o) =>
                {
                    // don't use the provider above. The command may be unwrapped by the time we get back here
                    var tableParameter = InsightDbProvider.For(cmd).CloneParameter(cmd, parameters.OfType <IDataParameter>().Single(p => p.Direction.HasFlag(ParameterDirection.Input)));
                    cmd.Parameters.Add(tableParameter);
                    ListParameterHelper.AddListParameter(tableParameter, o, cmd);
                });
            }

            // get the mapping of the properties for the type
            var mappings = ColumnMapping.Parameters.CreateMapping(type, null, command, parameters, null, 0, parameters.Count, true);

            // start creating a dynamic method
            Type typeOwner = type.HasElementType ? type.GetElementType() : type;
            var  dm        = new DynamicMethod(String.Format(CultureInfo.InvariantCulture, "CreateInputParameters-{0}", Guid.NewGuid()), null, new[] { typeof(IDbCommand), typeof(object) }, typeOwner, true);
            var  il        = dm.GetILGenerator();

            // copy the parameters into the command object
            var parametersLocal = il.DeclareLocal(typeof(IDataParameter[]));

            new StaticFieldStorage(provider).EmitLoad(il);
            il.Emit(OpCodes.Ldarg_0);
            new StaticFieldStorage(parameters).EmitLoad(il);
            il.Emit(OpCodes.Call, typeof(InsightDbProvider).GetMethod("CopyParameters", BindingFlags.NonPublic | BindingFlags.Instance));
            il.Emit(OpCodes.Stloc, parametersLocal);

            // go through all of the mappings
            for (int i = 0; i < mappings.Length; i++)
            {
                var mapping     = mappings[i];
                var dbParameter = parameters[i];

                // if there is no mapping for the parameter
                if (mapping == null)
                {
                    // sql will silently eat table parameters that are not specified, and that can be difficult to debug
                    if (provider.IsTableValuedParameter(command, dbParameter))
                    {
                        throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Table parameter {0} must be specified", dbParameter.ParameterName));
                    }

                    // unspecified input parameters get skipped
                    if (dbParameter.Direction == ParameterDirection.Input)
                    {
                        parameters[i] = null;
                    }

                    continue;
                }

                // get the parameter
                il.Emit(OpCodes.Ldloc, parametersLocal);
                il.Emit(OpCodes.Ldc_I4, i);
                il.Emit(OpCodes.Ldelem, typeof(IDataParameter));

                var prop = mapping.ClassPropInfo;

                // look up the best type to use for the parameter
                DbType sqlType = LookupDbType(mapping, dbParameter.DbType);

                // give the provider an opportunity to fix up the template parameter (e.g. set UDT type names)
                provider.FixupParameter(command, dbParameter, sqlType, prop.MemberType);

                ///////////////////////////////////////////////////////////////
                // We have a parameter, start handling all of the other types
                ///////////////////////////////////////////////////////////////

                ///////////////////////////////////////////////////////////////
                // Get the value from the object onto the stack
                ///////////////////////////////////////////////////////////////
                il.Emit(OpCodes.Ldarg_1);
                prop.EmitGetValue(il);

                ///////////////////////////////////////////////////////////////
                // Special case support for enumerables. If the type is -1 (our workaround, then call the list parameter method)
                ///////////////////////////////////////////////////////////////
                if (sqlType == DbTypeEnumerable)
                {
                    // we have the parameter and the value as object, add the command
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Call, typeof(ListParameterHelper).GetMethod("AddListParameter", BindingFlags.Static | BindingFlags.NonPublic));
                    continue;
                }

                // if this is a value type, then box the value so the compiler can check the type and we can call methods on it
                if (prop.MemberType.IsValueType)
                {
                    il.Emit(OpCodes.Box, prop.MemberType);
                }

                // special conversions for timespan to datetime
                if ((sqlType == DbType.Time && dbParameter.DbType != DbType.Time) ||
                    (dbParameter.DbType == DbType.DateTime || dbParameter.DbType == DbType.DateTime2 || dbParameter.DbType == DbType.DateTimeOffset))
                {
                    IlHelper.EmitLdInt32(il, (int)dbParameter.DbType);
                    il.Emit(OpCodes.Call, typeof(TypeConverterGenerator).GetMethod("ObjectToSqlDateTime"));
                }

                // if it's class type, boxed value type (in an object), or nullable, then we have to check for null
                Label readyToSetLabel = il.DefineLabel();
                if (!prop.MemberType.IsValueType || Nullable.GetUnderlyingType(prop.MemberType) != null)
                {
                    Label notNull = il.DefineLabel();

                    // check to see if it's not null
                    il.Emit(OpCodes.Dup);
                    il.Emit(OpCodes.Brtrue_S, notNull);

                    // it's null. replace the value with DbNull
                    il.Emit(OpCodes.Pop);
                    il.Emit(OpCodes.Ldsfld, _dbNullValue);

                    // value is set to null. ready to set the property.
                    il.Emit(OpCodes.Br_S, readyToSetLabel);

                    // we know the value is not null
                    il.MarkLabel(notNull);
                }

                ///////////////////////////////////////////////////////////////
                // if this is a linq binary, convert it to a byte array
                ///////////////////////////////////////////////////////////////
                if (prop.MemberType == TypeHelper.LinqBinaryType)
                {
                    il.Emit(OpCodes.Callvirt, TypeHelper.LinqBinaryToArray);
                }
                else if (prop.MemberType == typeof(XmlDocument))
                {
                    // we are sending up an XmlDocument. ToString just returns the classname, so use the outerxml.
                    il.Emit(OpCodes.Callvirt, prop.MemberType.GetProperty("OuterXml").GetGetMethod());
                }
                else if (prop.MemberType == typeof(XDocument))
                {
                    // we are sending up an XDocument. Use ToString.
                    il.Emit(OpCodes.Callvirt, prop.MemberType.GetMethod("ToString", new Type[] { }));
                }
                else if (prop.MemberType.GetInterfaces().Contains(typeof(IConvertible)) || prop.MemberType == typeof(object))
                {
                    // if the type supports IConvertible, then let SQL convert it
                    // if the type is object, we can't do anything, so let SQL attempt to convert it
                }
                else if (!TypeHelper.IsAtomicType(prop.MemberType))
                {
                    if (mapping.Serializer != null)
                    {
                        var serializerMethod = mapping.Serializer.GetMethod("Serialize", BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(object), typeof(Type) }, null);
                        if (serializerMethod == null)
                        {
                            throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Serializer type {0} needs the method 'public static string Serialize(object, Type)'", mapping.Serializer.Name));
                        }

                        il.EmitLoadType(prop.MemberType);
                        il.Emit(OpCodes.Call, serializerMethod);
                    }
                    else if (dbParameter.DbType != DbType.Object)
                    {
                        // it's not a system type and it's not IConvertible, so let's add it as a string and let the data engine convert it.
                        il.Emit(OpCodes.Callvirt, prop.MemberType.GetMethod("ToString", new Type[] { }));
                    }

                    // if we touched the value, it's possible that serializing returns a null
                    if (dbParameter.DbType != DbType.Object)
                    {
                        Label internalValueIsNotNull = il.DefineLabel();

                        // check to see if it's not null
                        il.Emit(OpCodes.Dup);
                        il.Emit(OpCodes.Brtrue_S, internalValueIsNotNull);

                        // it's null. replace the value with DbNull
                        il.Emit(OpCodes.Pop);
                        il.Emit(OpCodes.Ldsfld, _dbNullValue);

                        il.MarkLabel(internalValueIsNotNull);
                    }
                }

                ///////////////////////////////////////////////////////////////
                // p.Value = value
                ///////////////////////////////////////////////////////////////
                // push parameter is at top of method
                // value is above
                il.MarkLabel(readyToSetLabel);
                if (prop.MemberType == typeof(string))
                {
                    il.Emit(OpCodes.Call, typeof(DbParameterGenerator).GetMethod("SetParameterStringValue", BindingFlags.NonPublic | BindingFlags.Static));
                }
                else
                {
                    il.Emit(OpCodes.Callvirt, _iDataParameterSetValue);
                }
            }

            il.Emit(OpCodes.Ret);

            return((Action <IDbCommand, object>)dm.CreateDelegate(typeof(Action <IDbCommand, object>)));
        }
Example #4
0
        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);
        }
Example #5
0
        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>
        /// 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&lt;IDataReader, T&gt;.</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);
        }