Search code examples
c#datareader

Generic Relational to Composite C# Object Mapper


I have following code that's capable of mapping Reader to simple objects. The trouble is in case the object is composite it fails to map. I am not able to perform recursion by checking the property if it is a class itself

prop.PropertyType.IsClass as Type is required to call DataReaderMapper(). Any idea on how this may be achieved or some other approach? Also, currently I am not wishing to use any ORM.

public static class MapperHelper
{

    /// <summary>
    /// extension Method for Reader :Maps reader to type defined
    /// </summary>
    /// <typeparam name="T">Generic type:Model Class Type</typeparam>
    /// <param name="dataReader">this :current Reader</param>
    /// <returns>List of Objects</returns>
    public static IEnumerable<T> DataReaderMapper<T>(this IDataReader dataReader)where T : class, new()
    {
        T obj = default(T);

        //optimized taken out of both foreach and while loop
        PropertyInfo[] PropertyInfo;
        var temp = typeof(T);
        PropertyInfo = temp.GetProperties();

        while (dataReader.Read())
        {  
            obj = new T();

            foreach (PropertyInfo prop in PropertyInfo)
            {
                if (DataConverterHelper.ColumnExists(dataReader,prop.Name) && !dataReader.IsDBNull(prop.Name))
                {
                    prop.SetValue(obj, dataReader[prop.Name], null);
                }
            }
            yield return obj;

        }
    }
}

Solution

  • Don't make DataReaderMapper recursive. Just make the mapping part recursive:

    static void Assign(IDataReader reader, object instance) {
            foreach (PropertyInfo prop in PropertyInfo)
            {
                if (IsValue(prop))
                {
                    prop.SetValue(obj, dataReader[prop.Name], null);
                }
                else if (IsClass(prop)) {
                   var subInstance = Activator.CreateInstance(prop.PropertyType);
                    prop.SetValue(obj, subInstance, null);
                   Assign(subInstance, dataReader);
                }
            }
    }
    

    Like that. This recursively initializes all class type properties with default constructed instances and assigns data reader values to them.

    The code is clearly simplified. I elided some of your stuff and IsValue/IsClass are left to implement to your liking. Also, you probably want to use a naming scheme so that a.b.c as a column name maps to that property. That's doable by passing the current name prefix as a parameter to Assign.

    Further note, that DataReaderMapper being generic isn't required. I'm saying this because you struggled with that. Replace typeof(T) with a Type parameter and return an IEnumerable<object>. Then call Cast<T>() on the result of your method. So you see that this algorithm can in principle work without generics.