2010-11-05 191 views
19

Duplicar posibles:
DataTable to Generic List (memory leak?)Convertir DataTable a lista genérica en C#

Duplicar posibles:
Convert DataTable to List<>

Duplicar posibles:
Fastest way to convert datatable to generic list

responsabilidad: Sé que es preguntado en tantos lugares al SO.
Mi consulta es un poco diferente.

lenguaje de codificación: C# 3.5

tengo un DataTable llamado cardsTable que extraer datos de DB y tengo unas tarjetas de clase que sólo tienen algunas propiedades (ningún constructor)

public class Cards 
{ 
    public Int64 CardID { get; set; } 
    public string CardName { get; set; } 
    public Int64 ProjectID { get; set; } 
    public Double CardWidth { get; set; } 
    public Double CardHeight { get; set; } 
    public string Orientation { get; set; } 
    public string BackgroundImage { get; set; } 
    public string Background { get; set; } 
} 

quiero insertar los datos de tabla de tarjetas a un objeto de tipo Lista.
Mis datos tendrán campos nulos y, por lo tanto, el método no debería tener errores cuando convierta los datos. ¿El siguiente método es la mejor manera?

DataTable dt = GetDataFromDB(); 
List<Cards> target = dt.AsEnumerable().ToList().ConvertAll(x => new Cards { CardID = (Int64)x.ItemArray[0] }); 
+0

¿Se cierra esta pregunta o fue un error? :) –

+0

@marc garvell: Quiero manipular la datatable data también ... – naveen

+1

tienes un constructor ... el predeterminado. –

Respuesta

29

en realidad podría acortar considerablemente. Puede pensar en el método de extensión Select() como un convertidor de tipo.La conversión podría entonces ser escrito como esto:

List<Cards> target = dt.AsEnumerable() 
    .Select(row => new Cards 
    { 
     // assuming column 0's type is Nullable<long> 
     CardID = row.Field<long?>(0).GetValueOrDefault(), 
     CardName = String.IsNullOrEmpty(row.Field<string>(1)) 
      ? "not found" 
      : row.Field<string>(1), 
    }).ToList(); 
+0

@Jeff: ¿Y qué pasa con el tipo de columnas no puede contener nulos ... como CardName = row.Field (0) – naveen

+1

Mencioné que se acortaba considerablemente. En la superficie, no se ve muy diferente. Lo que quería decir es que podría escribirse de manera más eficiente. Su enfoque original crea dos instancias de listas diferentes que iteran la longitud de la tabla dos veces. Este enfoque realiza la conversión en una sola pasada. Para que lo sepas. –

+1

@naveen: las cadenas son ciertamente anulables, bien son tipos de referencia, no el tipo de valor que admite nulos. ¿Hay algo en particular que quisieras hacer con eso? –

2

así su la solución de una línea

que depende de si está o no sabe los datos en la base de datos es todo válida y no contener nada que rompa el anterior

por ejemplo, un campo que admite nulos cuando no lo esperas, tal vez debido a un join de la izquierda e sql que genera los datos.

De modo que si ha validado los datos antes de eso, sí - yo era bueno para sugerir algunos linq - pero los bajó.

Si necesita alguna validación, probablemente solo deba recorrer las filas de datos, generar su objeto como se indica arriba y agregarlo a la colección ... esto también le permitirá manejar los errores en una fila y seguir procesando el resto.

Esa es la manera en que yo lo veo todos modos

(maldito vine a downvote algo por lo que mi representante, era 1024)

6

El .ToList() está en el lugar equivocado, y si algunos campos pueden ser nula que tendrá que hacer frente a estos como ellos no van a convertir Int64 si son nulos

DataTable dt = GetDataFromDB(); 
List<Cards> target = dt.AsEnumerable().Select(
    x => new Cards { CardID = (Int64)(x.ItemArray[0] ?? 0) }).ToList(); 
+1

'ConvertAll()' como un método 'List '. No es un método de extensión ni 'IEnumerable ' lo define explícitamente. El análogo más cercano en 'IEnumerable ' sería 'Select()'. –

+0

Buena captura. actualizado. – Jamiec

9

Creo que todas las soluciones se pueden mejorar y hacer que el método más general si utiliza algunas convenciones y reflexión. Digamos que nombra sus columnas en la tabla de datos con el mismo nombre que las propiedades en su objeto, luego puede escribir algo que mire todas las propiedades de su objeto y luego busque esa columna en la tabla de datos para asignar el valor.

hice lo contrario, es decir ... de IList a tabla de datos, y el código que he escrito se puede ver en: http://blog.tomasjansson.com/convert-datatable-to-generic-list-extension/

No debería ser tan difícil de ir a otro lado, y debe Sería tan difícil sobrecargar las funciones para que pueda proporcionar información sobre qué propiedades desea incluir o excluir.

EDIT: Así que el código para que funcione es:

public static class DataTableExtensions 
{ 
    private static Dictionary<Type,IList<PropertyInfo>> typeDictionary = new Dictionary<Type, IList<PropertyInfo>>(); 
    public static IList<PropertyInfo> GetPropertiesForType<T>() 
    { 
     var type = typeof(T); 
     if(!typeDictionary.ContainsKey(typeof(T))) 
     { 
      typeDictionary.Add(type, type.GetProperties().ToList()); 
     } 
     return typeDictionary[type]; 
    } 

    public static IList<T> ToList<T>(this DataTable table) where T : new() 
    { 
     IList<PropertyInfo> properties = GetPropertiesForType<T>(); 
     IList<T> result = new List<T>(); 

     foreach (var row in table.Rows) 
     { 
      var item = CreateItemFromRow<T>((DataRow)row, properties); 
      result.Add(item); 
     } 

     return result; 
    } 

    private static T CreateItemFromRow<T>(DataRow row, IList<PropertyInfo> properties) where T : new() 
    { 
     T item = new T(); 
     foreach (var property in properties) 
     { 
      property.SetValue(item, row[property.Name], null); 
     } 
     return item; 
    } 

} 

Si usted tiene un DataTable puede simplemente escribir yourTable.ToList<YourType>() y se creará la lista para usted. Si tiene un tipo más complejo con objetos anidados, debe actualizar el código. Una sugerencia es simplemente sobrecargar el método ToList para aceptar un params string[] excludeProperties que contiene todas sus propiedades que no deberían mapearse. Por supuesto, puede agregar alguna comprobación nula en el foreach bucle del método CreateItemForRow.

ACTUALIZACIÓN: Se agregó el diccionario estático para almacenar el resultado de la operación de reflexión para que sea un poco más rápido. No he compilado el código, pero debería funcionar :).

+0

En 'CreateItemFromRow' deberías marcar' if (row.Table.Columns.Contains (property.Name)) 'y luego emitir' property.SetValue (.. '. Esto asegurará que no se lanzan excepciones si hay es un nombre de propiedad que no está en la fila de datos. – user20358

3

Solo un poco de simplificación. No uso ItemArray:

List<Person> list = tbl.AsEnumerable().Select(x => new Person 
        { 
         Id = (Int32) (x["Id"]), 
         Name = (string) (x["Name"] ?? ""), 
         LastName = (string) (x["LastName"] ?? "") 
        }).ToList(); 
+0

Gracias amigo. Me ayudó. –

0

Puede asignar la tabla de datos a la clase de modelo utilizando una clase genérica como la de abajo.

clase genérica

public static class DataTableMappingtoModel 
    { 
     /// <summary> 
     /// Maps Data Table values to coresponded model propertise 
     /// </summary> 
     /// <typeparam name="T"></typeparam> 
     /// <param name="dt"></param> 
     /// <returns></returns> 
     public static List<T> MappingToEntity<T>(this DataTable dt) 
     { 
      try 
      { 
       var lst = new List<T>(); 
       var tClass = typeof (T); 
       PropertyInfo[] proInModel = tClass.GetProperties(); 
       List<DataColumn> proInDataColumns = dt.Columns.Cast<DataColumn>().ToList(); 
       T cn; 
       foreach (DataRow item in dt.Rows) 
       { 
        cn = (T) Activator.CreateInstance(tClass); 
        foreach (var pc in proInModel) 
        { 


          var d = proInDataColumns.Find(c => string.Equals(c.ColumnName.ToLower().Trim(), pc.Name.ToLower().Trim(), StringComparison.CurrentCultureIgnoreCase)); 
          if (d != null) 
           pc.SetValue(cn, item[pc.Name], null); 


        } 
        lst.Add(cn); 
       } 
       return lst; 
      } 
      catch (Exception e) 
      { 
       throw e; 
      } 
     } 
    } 

clase Modelo

public class Item 
{ 
    public string ItemCode { get; set; } 
    public string Cost { get; set; } 
    public override string ToString() 
    { 
     return "ItemCode : " + ItemCode + ", Cost : " + Cost; 
    } 
} 

Crear DataTable

public DataTable getTable() 
{ 
    DataTable dt = new DataTable(); 
    dt.Columns.Add(new DataColumn("ItemCode", typeof(string))); 
    dt.Columns.Add(new DataColumn("Cost", typeof(string))); 
    DataRow dr; 
    for (int i = 0; i < 10; i++) 
    { 
     dr = dt.NewRow(); 
     dr[0] = "ItemCode" + (i + 1); 
     dr[1] = "Cost" + (i + 1); 
     dt.Rows.Add(dr); 
    } 
    return dt; 
} 

Ahora podemos convertir esta DataTable a la lista, como a continuación:

DataTable dt = getTable(); 
List<Item> lst = dt.ToCollection<Item>(); 
foreach (Item cn in lst) 
{ 
    Response.Write(cn.ToString() + "<BR/>"); 
} 

Esperanza le ayudará a

0

he construido en la parte superior de la lógica de Tomas Jansson para incluir un atributo "Ignorar". Esto me permite agregar otros atributos a la clase que se está cargando sin romper la carga de DataTable-to-Class.

Alternativamente, también consideré agregar un parámetro separado que contenga el nombre de columna real desde el que se va a leer en DataTable. En ese caso, en lugar de usar "row [property.Name]", usaría la fila [attribute.Name] "o algo así para esa propiedad en particular.

public static class DataTableExtensions 
{ 
    [AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = true)] 
    public sealed class IgnoreAttribute : Attribute { public IgnoreAttribute() { } } 

    private static Dictionary<Type, IList<PropertyInfo>> typeDictionary = new Dictionary<Type, IList<PropertyInfo>>(); 

    public static IList<PropertyInfo> GetPropertiesForType<T>() 
    { 
     var type = typeof(T); 

     if (!typeDictionary.ContainsKey(typeof(T))) 
      typeDictionary.Add(type, type.GetProperties().ToList()); 

     return typeDictionary[type]; 
    } 

    public static IList<T> ToList<T>(this DataTable table) where T : new() 
    { 
     IList<PropertyInfo> properties = GetPropertiesForType<T>(); 
     IList<T> result = new List<T>(); 

     foreach (var row in table.Rows) 
      result.Add(CreateItemFromRow<T>((DataRow)row, properties)); 

     return result; 
    } 

    private static T CreateItemFromRow<T>(DataRow row, IList<PropertyInfo> properties) where T : new() 
    { 
     T item = new T(); 

     foreach (var property in properties) 
     { 
      // Only load those attributes NOT tagged with the Ignore Attribute 
      var atr = property.GetCustomAttribute(typeof(IgnoreAttribute)); 
      if (atr == null) 
       property.SetValue(item, row[property.Name], null); 
     } 

     return item; 
    } 
} 
0

Llegando tarde pero esto puede ser útil. Se puede llamar usando:

table.Map(); o llame con un Func para filtrar los valores.

Incluso puede cambiar el nombre de la asignación entre la propiedad de tipo y el encabezado DataColumn estableciendo los atributos en la propiedad.

[AttributeUsage(AttributeTargets.Property)] 
    public class SimppleMapperAttribute: Attribute 
    { 
     public string HeaderName { get; set; } 
    } 


    public static class SimpleMapper 
{ 
    #region properties 
    public static bool UseDeferredExecution { get; set; } = true; 
    #endregion 

#region public_interface 


    public static IEnumerable<T> MapWhere<T>(this DataTable table, Func<T, bool> sortExpression) where T:new() 
    { 
     var result = table.Select().Select(row => ConvertRow<T>(row, table.Columns, typeof(T).GetProperties())).Where((t)=>sortExpression(t)); 
     return UseDeferredExecution ? result : result.ToArray(); 
    } 
    public static IEnumerable<T> Map<T>(this DataTable table) where T : new() 
    { 
     var result = table.Select().Select(row => ConvertRow<T>(row, table.Columns, typeof(T).GetProperties())); 
     return UseDeferredExecution ? result : result.ToArray(); 
    } 
    #endregion 

#region implementation_details 
    private static T ConvertRow<T>(DataRow row, DataColumnCollection columns, System.Reflection.PropertyInfo[] p_info) where T : new() 
    { 
     var instance = new T(); 
     foreach (var info in p_info) 
     { 
      if (columns.Contains(GetMappingName(info))) SetProperty(row, instance, info);    
     } 
     return instance; 
    } 

    private static void SetProperty<T>(DataRow row, T instance, System.Reflection.PropertyInfo info) where T : new() 
    { 
     string mp_name = GetMappingName(info); 
     object value = row[mp_name]; 
     info.SetValue(instance, value); 
    } 

    private static string GetMappingName(System.Reflection.PropertyInfo info) 
    { 
     SimppleMapperAttribute attribute = info.GetCustomAttributes(typeof(SimppleMapperAttribute),true).Select((o) => o as SimppleMapperAttribute).FirstOrDefault(); 
     return attribute == null ? info.Name : attribute.HeaderName; 
    } 
#endregion 
}