Convert ListaGenerica a DataTable

image

 

Me apunto por aquí un trozo de código que permite convertir una Lista Genérica (por ejemplo de entidades) a un DataTable. Es totalmente compatible con el Framework 2.

Para convertir una Lista Genérica en un DataTable, debemos primeramente obtener por reflexión las propiedades públicas de los elementos de la lista genérica. Cada una de estas propiedades corresponderá a una columna de la Tabla.

A continuación, deberemos recorrer los elementos de la lista genérica, y añadir una fila a la tabla por cada item de la lista.

Hay que tener cuidado con los tipos nullables. No están soportados en los DataTables.

 

/// <summary>
/// Convert data from a generyc list to a DataTable
/// </summary>
public static DataTable Convert<TItemType>(List<TItemType> list)
{
    DataTable convertedData = new DataTable();

    // Get List Item Properties info
    Type itemType = typeof(TItemType);
    PropertyInfo[] publicProperties =
        // Only public non inherited properties
        itemType.GetProperties(BindingFlags.Instance | BindingFlags.Public);

    // Create Table Columns
    foreach (PropertyInfo property in publicProperties)
    {
        // DataSet does not support System.Nullable<>
        if (property.PropertyType.IsGenericType &&
            property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
        {
            // Set the column datatype as the nullable value type
            convertedData.Columns.Add(property.Name, property.PropertyType.GetGenericArguments()[0]);
        }
        else
        {
            convertedData.Columns.Add(property.Name, property.PropertyType);
        }
    }

    // Convert the Data
    foreach (TItemType item in list)
    {
        object[] rowData = new object[convertedData.Columns.Count];
        int rowDataIndex = 0;
        // Iterate through Item Properties
        foreach (PropertyInfo property in publicProperties)
        {
            // Add a single cell data
            rowData[rowDataIndex] = property.GetValue(item, null);
            rowDataIndex++;
        }
        convertedData.Rows.Add(rowData);
    }

    return convertedData;
}

 

Y la operación inversa; Convertir una DataTable en una lista Genérica.

En este caso, lo primero será ver qué columnas de la tabla son propiedades públicas en los elementos de la lista. Las demás, no podré convertirlas, o al menos no directamente.

A continuación, recorremos las filas de la tabla, e instanciamos un elemento de la lista por cada fila. Asignamos por reflexión los valores a las propiedades, y añadimos la nueva instancia a la lista.

Este código también tiene en cuenta las conversiones entre Null y System.DBNull. Recordemos que en los DataTable se utiliza este valor para indicar un valor nulo. Esto seguramente es así, porque los DataTable son anteriores a la aparición de los tipos Nullables.

 

/// <summary>
/// Fills a generyc list with the data cointained in a DataTable
/// </summary>
public static List<TItemType> Convert<TItemType>(DataTable dt) where TItemType:new()
{
    List<TItemType> convertedData = new List<TItemType>();

    // Get List Item Properties info
    Type itemType = typeof(TItemType);
    
    // Only public non inherited properties
    BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public;

    // wich columns of the datatable are properties of TItemType?
    Dictionary<string,PropertyInfo> availableProperties = new Dictionary<string, PropertyInfo>();
    foreach (DataColumn column in dt.Columns)
    {
        PropertyInfo prop = itemType.GetProperty(column.ColumnName, bindingFlags);
        if(prop!=null) availableProperties.Add(column.ColumnName,prop);
    }

    // Fill the generyc list with data
    foreach (DataRow row in dt.Rows)
    {
        TItemType item = new TItemType();
        foreach (KeyValuePair<string, PropertyInfo> availableProperty in availableProperties)
        {
            object propValue = row[availableProperty.Key];
            if (propValue!=null&&propValue!=System.DBNull.Value)
                availableProperty.Value.SetValue(item, propValue, null);                

        }
        convertedData.Add(item);
    }

    return convertedData;
}

 

Por último, cómo probaríamos este código? Unit Testing a Tope!

Por ejemplo, puedo declarar una entidad de prueba, cargar una lista con datos aleatorios, y ver que tras la conversión a DataTable, la Tabla tiene tantas columnas como propiedades públicas tiene la entidad, y que el número de elementos coincide.

Y al hacer la operación inversa, ver que el número de elementos es el mismo.

 

List<TestEntity> myItems = new List<TestEntity>();

// Generate Random Data
Random r = new Random(System.DateTime.Now.Millisecond);
for (int i = 0; i < 10; i++)
{
    TestEntity newItem = new TestEntity();
    newItem.Name = Guid.NewGuid().ToString();
    newItem.Birthday = DateTime.Now.AddHours(r.Next(1000));
    newItem.LastBillDate = r.Next()%2 == 0 ? null : new DateTime?(DateTime.Now.AddHours(r.Next(1000)));
    newItem.LastBillAmount = r.Next() % 2 == 0 ? null : new double?(r.NextDouble());
    myItems.Add(newItem);
}

DataTable dt1 = Converter.Convert<TestEntity>(myItems);
// The date table columns count and the public properties must be the same
Assert.AreEqual(dt1.Columns.Count,typeof(TestEntity).GetProperties(BindingFlags.Instance | BindingFlags.Public).Length);
Assert.AreEqual(dt1.Rows.Count,myItems.Count);

// Re convert to an Entity List
List<TestEntity> myConvertedItems = Converter.Convert<TestEntity>(dt1);
Assert.AreEqual(myItems.Count, myConvertedItems.Count);