简体   繁体   中英

Populating nullable type from SqlDataReader using Reflection.Emit

When trying to set value for any nullable datatype using reflection.emit, the values for nullable data types are not populated.

for eg

public class Employee
{
    public int Id { get;set;}
    public string? Name {get;set;}
    public DateTime? Joined {get;set;}
    public bool? IsManager {get;set;}
}

Here are my reflection.emit code to populate sql data reader:

        private static readonly MethodInfo GetValueMethod = typeof(SqlDataReader).GetMethod("get_Item", new Type[] { typeof(int) });
        private static readonly MethodInfo IsDBNullMethod = typeof(SqlDataReader).GetMethod("IsDBNull", new Type[] { typeof(int) }); 

        DynamicMethod method = new DynamicMethod("DynamicCreateMapping", typeof(T), new Type[] { typeof(IDataRecord) }, typeof(T), true);
        ILGenerator generator = method.GetILGenerator();

        LocalBuilder result = generator.DeclareLocal(typeof(T));
        generator.Emit(OpCodes.Newobj, typeof(T).GetConstructor(Type.EmptyTypes));
        generator.Emit(OpCodes.Stloc, result);

        for (int i = 0; i < reader.FieldCount; i++)
        {
            PropertyInfo propertyInfo = typeof(T).GetProperty(reader.GetName(i));
            Label endIfLabel = generator.DefineLabel();

            if (propertyInfo != null && propertyInfo.GetSetMethod() != null)
            {
                generator.Emit(OpCodes.Ldarg_0);
                generator.Emit(OpCodes.Ldc_I4, i);
                generator.Emit(OpCodes.Callvirt, IsDBNullMethod);
                generator.Emit(OpCodes.Brtrue, endIfLabel);

                generator.Emit(OpCodes.Ldloc, result);
                generator.Emit(OpCodes.Ldarg_0);
                generator.Emit(OpCodes.Ldc_I4, i);
                generator.Emit(OpCodes.Callvirt, GetValueMethod);
                generator.Emit(OpCodes.Unbox_Any, reader.GetFieldType(i));
                generator.Emit(OpCodes.Callvirt, propertyInfo.GetSetMethod());

                generator.MarkLabel(endIfLabel);
            }
        }

        generator.Emit(OpCodes.Ldloc, result);
        generator.Emit(OpCodes.Ret);

I know the problem exist on the below line but not sure how to fix it:

generator.Emit(OpCodes.Unbox_Any, reader.GetFieldType(i));

I try to set but it failed using:

generator.Emit(OpCodes.Unbox_Any, propertyInfo.PropertyType);

So how to match the datatype from class and not from the sql data reader?

I fixed it after breaking head for quite sometime. Thanks to the link [ http://tillitsonpaper.blogspot.co.uk/2011/05/mapping-data-reader-to-object.html][1] . I did modified few codes and am pasting it here so that it might help newbie who has very little knowledge of reflection.Emit like me.

I added condition to check for Boolean and Enum types:

    if (propertyInfo.PropertyType.IsEnum || propertyInfo.PropertyType == typeof(Boolean))
     {
           generator.Emit(OpCodes.Unbox_Any, reader.GetFieldType(i));
     }
     else
     {
         generator.Emit(OpCodes.Call, GetConverterMethod(propertyInfo.PropertyType));
     }

The else part above will handle any type including nullable types, The GetConverterMethod implementation is shown below:

        private MethodInfo GetConverterMethod(Type type)
        {
            switch (type.Name.ToUpper())
            {
                case "INT16":
                    return CreateConverterMethodInfo("ToInt16");
                case "INT32":
                    return CreateConverterMethodInfo("ToInt32");
                case "INT64":
                    return CreateConverterMethodInfo("ToInt64");
                case "SINGLE":
                    return CreateConverterMethodInfo("ToSingle");
                case "BOOLEAN":
                    return CreateConverterMethodInfo("ToBoolean");
                case "STRING":
                    return CreateConverterMethodInfo("ToString");
                case "DATETIME":
                    return CreateConverterMethodInfo("ToDateTime");
                case "DECIMAL":
                    return CreateConverterMethodInfo("ToDecimal");
                case "DOUBLE":
                    return CreateConverterMethodInfo("ToDouble");
                case "GUID":
                    return CreateConverterMethodInfo("ToGuid");
                case "BYTE[]":
                    return CreateConverterMethodInfo("ToBytes");
                case "BYTE":
                    return CreateConverterMethodInfo("ToByte");
                case "NULLABLE`1":
                    {
                        if (type == typeof(DateTime?))
                        {
                            return CreateConverterMethodInfo("ToDateTimeNull");
                        }
                        else if (type == typeof(Int32?))
                        {
                            return CreateConverterMethodInfo("ToInt32Null");
                        }
                        else if (type == typeof(Boolean?))
                        {
                            return CreateConverterMethodInfo("ToBooleanNull");
                        }
                        break;
                    }
            }
            return null;
        }

        private MethodInfo CreateConverterMethodInfo(string method)
        {
            return typeof(Converter).GetMethod(method, new Type[] { typeof(object) });
        }

The Converter Class code is below:

    public class Converter
    {
        public static Int16 ToInt16(object value)
        {
            return ChangeType<Int16>(value);
        }

        public static Int32 ToInt32(object value)
        {
            return ChangeType<Int32>(value);
        }

        public static Int64 ToInt64(object value)
        {
            return ChangeType<Int64>(value);
        }

        public static Single ToSingle(object value)
        {
            return ChangeType<Single>(value);
        }

        public static Boolean ToBoolean(object value)
        {
            return ChangeType<Boolean>(value);
        }

        public static System.String ToString(object value)
        {
            return ChangeType<System.String>(value);
        }

        public static DateTime ToDateTime(object value)
        {
            return ChangeType<DateTime>(value);
        }

        public static Decimal ToDecimal(object value)
        {
            return ChangeType<Decimal>(value);
        }

        public static Double ToDouble(object value)
        {
            return ChangeType<Double>(value);
        }

        public static Guid ToGuid(object value)
        {
            return ChangeType<Guid>(value);
        }

        public static Byte ToByte(object value)
        {
            return ChangeType<Byte>(value);
        }

        public static Byte[] ToBytes(object value)
        {
            return ChangeType<Byte[]>(value);
        }
        public static DateTime? ToDateTimeNull(object value)
        {
            return ChangeType<DateTime?>(value);
        }

        public static System.Int32? ToInt32Null(object value)
        {
            return ChangeType<Int32?>(value);
        }

        public static Boolean? ToBooleanNull(object value)
        {
            return ChangeType<Boolean?>(value);
        }

        private static T ChangeType<T>(object value)
        {
            var t = typeof(T);

            if (t.IsGenericType && t.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
            {
                if (value == null)
                {
                    return default(T);
                }

                t = Nullable.GetUnderlyingType(t); ;
            }

            return (T)Convert.ChangeType(value, t);
        }
    }

Might not be very elegant way of doing but this works for any nullable types as well. Hope this helps to someone who is struggling to get Nullable types working with Reflection.Emit.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM