miércoles, 18 de septiembre de 2013

Convertir dataReader en un tipo IEnumerable a través de reflexión con c#

En otro post puse un ejemplo de reflexión.
Ahora vamos a ver otro uso de la reflexión, en este caso para convertir un dataReader en un lista genérica, es decir en un tipo IEnumerable<T>

El escenario es útil en los siguientes casos:

  • Trabajamos con LINQ y nos hemos acostumbrado a utilizarlo para tratar las entidades de negocio.
  • Si trabajamos con un ORM (Entity, NHibernate) enlazaremos fácilmente las entidades de negocio y consultas con listas.
  • Pero, si, no trabajamos con un ORM, no lo tendremos tan cómodo.
  • Tampoco si trabajamos con un ORM con enfoque Code First y obtenemos los datos de un procedimiento almacenado.
  • Independientemente de la utilidad del método, queremos ver un ejemplo de método de extensión y/o reflexión.

Paso 1. Crear Método de reflexión.
Recibe por parámetro:
 Un Objeto al que copiar el valor de la propiedad. Observar que es un paso por referencia.
 El nombre/Valor de la propiedad a establecer al objeto.

public class Reflection
{
    public void FillObjectWithProperty(ref object objectTo, string propertyName, object propertyValue)
   {
        Type tOb2 = objectTo.GetType();
         tOb2.GetProperty(propertyName).SetValue(objectTo, propertyValue);
   }
}
Ya tenemos la función. No tiene ningún misterio, utiliza tres métodos básicos de reflexión que hablan por si solos (GetType, GetPropery, y SetValue).

Paso 2. Crear método de extensión.
Es el más complejo, podemos encontrar mucha información en google sobre los métodos de extensión. Pego la función, que está comentada con detalle.
public static class IENumerableExtensions
{
    public static IEnumerable<T> CopyDataReader<T>(this IEnumerable<T> list, DbDataReader dr)
    {
        //Instanciar objecto reflec de la clase Reflection codificada antes
        Reflection reflec = new Reflection();
        //Declarar un objeto "instance" tipo Object y una lista de objetos
        Object instance;
        List<Object> lstObj = new List<Object>();
       
        //Recorrer dataReader
       while (dr.Read()){
            //Crear instancia del objeto necesario. La instancia se crea obteniendo el 
            //tipo de objeto T del objeto list, que es el propio objeto que llama al método de extensión.
            //Es decir se infiere el tipo T y se instancia
            instance= Activator.CreateInstance(list.GetType().GetGenericArguments()[0]);
            
            //Se recorren todos los campos de cada fila del dataReader, y mediante
            //el objeto reflec (método del primer paso) rellenamos el objeto instance con los valores
            //del datareader
        foreach (DataRow drow in dr.GetSchemaTable().Rows){
                    reflec.FillObjectWithProperty(ref instance, drow.ItemArray[0].ToString(), dr[drow.ItemArray[0].ToString()]);
             }
            
            //Añadir objeto instance a la lista
            lstObj.Add(instance);
        }
       
        //Convertir la lista de objetos (lstObj) en la lista List<T> resultante(lstResult)
        List<T> lstResult = new List<T>();
        foreach (Object item in lstObj){
             lstResult.Add((T)Convert.ChangeType(item, typeof(T)));
        }


         return lstResult;
     }
 }


Paso 3. Llamar al método de extensión.
Dado un dataReader (dr)
 var listaTipoT= new List<T>();
 var listaResult = listaTipoT.CopyDataReader(dr).ToList();


Y ya tenemos un List T !!

2 comentarios:

  1. Yo he probado conversiones de este tipo, con Reflection y la verdad es que son muy potentes ya que sin apenas código mapeas objetos como dataTables a listas de objetos de tu dominio.

    El problema de esto es la eficiencia, es paupérrima. Cuando tienes que mapear muchos objetos mejor hacerlo directamente o con LINQ . En concreto tuve un caso en el que convertí 500000 de rows de un datatable a un IEnumerable de objetos de mi dominio y tardaba 14 segundos, sin embargo con LINQ lo hacía en 1 segundo. La diferencia es abismal

    ResponderEliminar
  2. Sí, el tema de que es muy lento me lo ha comentado más gente. Una solución que proponían es guardar el mañeado de tipos del datareder en la primera llamada a la función, y así hacer el gettype solo para una fila. Aún no lo he probado pero supongo que mejorará el rendimiento.

    ResponderEliminar