DataReader Extension to map DataReader with object or list of objects

While reading data from DataReader, many times we need to map that data either in a single object or list of objects. We generally do it manually by reading data index wise or column name wise and set in properties of object(s). If there is any change in backend result set (add/delete column), we will have to manually fix it in code. Here I have created a DataReader Extension to solve this issue and make mapping easy.

Old style of mapping data from Data Reader to Object

Map data from DataReader to single object

dr = cmd.ExecuteReader();
if (dr.HasRows)
{
    dr.Read();
    objCustomer = new Customer
    {
        Id = Convert.ToInt32(dr[0].ToString()),
        FirstName = dr[1].ToString(),
        LastName = dr[2].ToString(),
        Address = dr[3].ToString(),
        Age = Convert.ToInt32(dr[4].ToString())
    };
}

Map data from DataReader to list of object

dr = cmd.ExecuteReader();
if (dr.HasRows)
{
    while (dr.Read())
    {
        Customer objCustomer = new Customer
        {
            Id = Convert.ToInt32(dr[0].ToString()),
            FirstName = dr[1].ToString(),
            LastName = dr[2].ToString(),
            Address = dr[3].ToString(),
            Age = Convert.ToInt32(dr[4].ToString())
        };
        objCustomerList.Add(objCustomer);
    }
}

But this is a bad code when we have a requirement to add new properties or delete old properties. We will have to remember index number or column name while adding/deleting new column for mapping. What will happen if we have 100 properties? :)

Solution

I have created DataReaderExtension which can be used to map object or list of objects with DataReader without handling index numbers or column names. You just need to add these classes in your project (Data Access Layer):

DataReaderExtension.cs

public static class DataReaderExtensions
{
    /// <Summary>
    /// Map data from DataReader to list of objects
    /// </Summary>
    /// <typeparam name="T">Object</typeparam>
    /// <param name="dr">Data Reader</param>
    /// <returns>List of objects having data from data reader</returns>
    public static List<T> MapToList<T>(this DbDataReader dr) where T : new()
    {
        List<T> RetVal = null;
        var Entity = typeof(T);
        var PropDict = new Dictionary<string, PropertyInfo>();
        try
        {
            if (dr != null && dr.HasRows)
            {
                RetVal = new List<T>();
                var Props = Entity.GetProperties(BindingFlags.Instance | BindingFlags.Public);
                PropDict = Props.ToDictionary(p => p.Name.ToUpper(), p => p);
                while (dr.Read())
                {
                    T newObject = new T();
                    for (int Index = 0; Index < dr.FieldCount; Index++)
                    {
                        if (PropDict.ContainsKey(dr.GetName(Index).ToUpper()))
                        {
                            var Info = PropDict[dr.GetName(Index).ToUpper()];
                            if ((Info != null) && Info.CanWrite)
                            {
                                var Val = dr.GetValue(Index);
                                Info.SetValue(newObject, (Val == DBNull.Value) ? null : Val, null);
                            }
                        }
                    }
                    RetVal.Add(newObject);
                }
            }
        }
        catch (Exception)
        {
            throw;
        }
        return RetVal;
    }
    /// <Summary>
    /// Map data from DataReader to an object
    /// </Summary>
    /// <typeparam name="T">Object</typeparam>
    /// <param name="dr">Data Reader</param>
    /// <returns>Object having data from Data Reader</returns>
    public static T MapToSingle<T>(this DbDataReader dr) where T : new()
    {
        T RetVal = new T();
        var Entity = typeof(T);
        var PropDict = new Dictionary<string, PropertyInfo>();
        try
        {
            if (dr != null && dr.HasRows)
            {
                var Props = Entity.GetProperties(BindingFlags.Instance | BindingFlags.Public);
                PropDict = Props.ToDictionary(p => p.Name.ToUpper(), p => p);
                dr.Read();
                for (int Index = 0; Index < dr.FieldCount; Index++)
                {
                    if (PropDict.ContainsKey(dr.GetName(Index).ToUpper()))
                    {
                        var Info = PropDict[dr.GetName(Index).ToUpper()];
                        if ((Info != null) && Info.CanWrite)
                        {
                            var Val = dr.GetValue(Index);
                            Info.SetValue(RetVal, (Val == DBNull.Value) ? null : Val, null);
                        }
                    }
                }
            }
        }
        catch (Exception)
        {
            throw;
        }
        return RetVal;
    }
}

TConverter.cs

public static class TConverter
{
    public static T ChangeType<T>(object value)
    {
        return (T)ChangeType(typeof(T), value);
    }
    public static object ChangeType(Type t, object value)
    {
        TypeConverter tc = TypeDescriptor.GetConverter(t);
        return tc.ConvertFrom(value);
    }
}

Now After using DataReaderExtension, we can map data from DataReader to object or list of objects like this:

Map data from DataReader to single object

dr = cmd.ExecuteReader();
objCustomer = dr.MapToSingle<Customer>();

Map data from DataReader to list of object

dr = cmd.ExecuteReader();
objCustomerList = dr.MapToList<Customer>();

Looking Simple? :)