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;
}
}
}
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.
My preference would be to leave the heavy lifting to the calling code. This avoids relatively slow recursion, and allows you to build objects where the field names don't line up exactly or that don't have default constructors:
public static class MapperHelper
{
public static IEnumerable<T> DataReaderMapper<T>(this IDataReader dataReader, Func<IDataRecord, T> map)
{
while (dataReader.Read())
{
yield return map(dataReader);
}
}
Then I would move your existing property code to a method that could be passed here as a default implementation:
public static T DefaultMapper<T>(IDataRecord record) where T : class, new()
{
//This is now effectively inside the while loop,
// but .Net caches the expensive recursive calls for you
PropertyInfo[] PropertyInfo;
var temp = typeof(T);
PropertyInfo = temp.GetProperties();
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);
}
}
return obj;
}
}
You could call it with the default mapper like this:
foreach (var record in myDataReader.DataReaderMapper<SomeType>(MapperHelper.DefaultMapper) )
{
//...
}
Of course this will still fail on complex composite types, but I don't see how you can expect any generic mapper to succeed in that scenario... if your inner type itself has properties to fill, there's no good way to get specify the matching name. This allows you quickly write your own mapper, if you need to:
foreach (var record in myDataRecord.DataReaderMapper<SomeType>(r => {
//complex mapping goes here
SomeType result = new SomeType() {
field1 = r["field1"],
field2 = new OtherType() {
subField = r["subField"],
otherField = r["otherField"]
}
}
return result;
})
{
//...
}
And of course you can always build the logic for that conversion into a method that could instead pass by name.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With