Map DB Records to Entity

May 29, 2011 at 7:15 PM
Edited May 29, 2011 at 8:05 PM

 

using System;
using System.Linq;
using System.Collections.Generic;
using System.Data.Common;
using System.Reflection;
using System.Reflection.Emit;
using BSoft.Collections;

public static class EntityMapper
{

    #region -- Attributes --

    [AttributeUsage(AttributeTargets.Property)]
    public class ResultColumnAttribute : Attribute
    {
    }

    [AttributeUsage(AttributeTargets.Class)]
    public class EntityMapperAfterMapAttribute : Attribute
    {
        public EntityMapperAfterMapAttribute(string afterMapFunctionName)
        {
            AfterMapFunctionName = afterMapFunctionName;
        }

        public string AfterMapFunctionName { get; set; }
    }
        
    #endregion

    private delegate T MapEntity<out T>(DbDataReader reader);
    private static readonly ThreadSafeDictionary<Type, Delegate> CachedMappers = new ThreadSafeDictionary<Type, Delegate>();

    #region -- Prepare Entity --

    private static void PrepareEntity<T>()
    {
        // If a mapping function from dr -> T does not exist, create and cache one
        if (!CachedMappers.ContainsKey(typeof(T)))
        {
            // Our method will take in a single parameter, a DbDataReader
            Type[] methodArgs = { typeof(DbDataReader) };

            // The MapDR method will map a DbDataReader row to an instance of type T
            var dm = new DynamicMethod("MapDR", typeof(T), methodArgs, Assembly.GetExecutingAssembly().GetType().Module);
            var il = dm.GetILGenerator();

            // The method that will be used to convert values type
            var typeConvertor = typeof (ObjectExtensions).GetMethod("ConvertTo", new[] {typeof (object)});

            // We'll have a single local variable, the instance of T we're mapping
            il.DeclareLocal(typeof(T));

            // Create a new instance of T and save it as variable 0
            il.Emit(OpCodes.Newobj, typeof(T).GetConstructor(Type.EmptyTypes));
            il.Emit(OpCodes.Stloc_0);

            // Attribute type we are going to search for
            Type dbColumnAttrType = typeof(ResultColumnAttribute);

            foreach (PropertyInfo pi in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance))
            {
                if (pi.GetCustomAttributes(dbColumnAttrType, true).Length <= 0 || pi.CanWrite == false) continue;

                // Load the T instance, SqlDataReader parameter and the field name onto the stack
                il.Emit(OpCodes.Ldloc_0);
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldstr, pi.Name);

                // Push the column value onto the stack
                il.Emit(OpCodes.Callvirt, typeof(DbDataReader).GetMethod("get_Item", new[] { typeof(string) }));

                // Depending on the type of the property, convert the datareader column value to the type
                switch (pi.PropertyType.Name)
                {
                    case "Int16": il.Emit(OpCodes.Call, typeConvertor.MakeGenericMethod(typeof(Int16))); break;
                    case "Int32": il.Emit(OpCodes.Call, typeConvertor.MakeGenericMethod(typeof(Int32))); break;
                    case "Int64": il.Emit(OpCodes.Call, typeConvertor.MakeGenericMethod(typeof(Int64))); break;
                    case "Boolean": il.Emit(OpCodes.Call, typeConvertor.MakeGenericMethod(typeof(Boolean))); break;
                    case "String": il.Emit(OpCodes.Call, typeConvertor.MakeGenericMethod(typeof(String))); break;
                    case "DateTime": il.Emit(OpCodes.Call, typeConvertor.MakeGenericMethod(typeof(DateTime))); break;
                    case "Decimal": il.Emit(OpCodes.Call, typeConvertor.MakeGenericMethod(typeof(Decimal))); break;

                    // Don't set the field value as it's an unsupported type
                    default:continue;
                }

                // Set the T instances property value
                il.Emit(OpCodes.Callvirt, typeof(T).GetMethod("set_" + pi.Name, new Type[] { pi.PropertyType }));
            }

            // Load the T instance onto the stack
            il.Emit(OpCodes.Ldloc_0);

            // Return
            il.Emit(OpCodes.Ret);

            // Cache the method so we won't have to create it again for the type T
            CachedMappers.Add(typeof(T), dm.CreateDelegate(typeof(MapEntity<T>)));
        }

    }
        
    #endregion

    public static IEnumerable<T> MapToEntities<T>(this DbDataReader reader, params object[] args)
    {
        // If a mapping function from dr -> T does not exist, create and cache one
        if (!CachedMappers.ContainsKey(typeof(T))) PrepareEntity<T>();

        // Get a delegate reference to the dynamic method
        var invokeMapEntity = (MapEntity<T>)CachedMappers[typeof(T)];

        // Function that would be called after data record mapped
        var afterMapAttribute = (EntityMapperAfterMapAttribute)typeof(T).GetCustomAttributes(false).Where(a => a.GetType() == typeof(EntityMapperAfterMapAttribute)).FirstOrDefault();

        // For each row, map the row to an instance of T and yield return it)
        while (reader.Read())
        {
            var result = invokeMapEntity(reader);

            if (afterMapAttribute != null)
            {
                result.GetType().InvokeMember(afterMapAttribute.AfterMapFunctionName, BindingFlags.InvokeMethod, null, result, args);
            }

            yield return result;
        }
    }

}

 

// =============
//  Usage tutorial
// =============
class UserInfo
{
     [EntityMapper.ResultColumn]
     public int Id { get; set; }

     [EntityMapper.ResultColumn]
     public string Name { get; set; }

     [EntityMapper.ResultColumn]
     public string Password { get; set; }
}


using (var reader = sqlCommand.ExecuteReader())
{
     var users = reader.MapToEntities<UserInfo>();
}
Sep 26, 2011 at 7:35 AM

 var val = reader[pi.Name];         

  Enum.Parse(pi.PropertyType, val.ToString());

How to put the code above added to it

Sep 26, 2011 at 3:15 PM

Guys, please apply for a developer access to the project. You will be granted and then able to download and modify the sources. Thanks for your support!