.net - La forma más rápida de convertir tablas de datos a listas genéricas
.net-2.0 datatable (5)
Tengo un método de selección de nivel de datos que devuelve una tabla de datos. Se llama desde un método de nivel empresarial que luego debe devolver una Lista genérica fuertemente tipada.
Lo que quiero hacer es muy similar (pero no lo mismo) a esta pregunta:
¿Cómo se convierte una DataTable en una lista genérica?
Lo que es diferente es que quiero que la lista contenga objetos fuertemente tipados en lugar de datarows (también, todavía no tengo linq disponible aquí).
Estoy preocupado por el rendimiento. El método de nivel de negocio se llamará a su vez desde el nivel de presentación, y los resultados se repetirán para que se muestren al usuario. Parece un desperdicio añadir una iteración adicional en el nivel comercial, solo hacerlo de nuevo para la presentación, así que quiero que sea lo más rápido posible.
Esta es una tarea común, así que estoy buscando un buen patrón que pueda repetirse una y otra vez.
El problema con la muestra anterior es que es terriblemente lento. Tengo una DataTable con alrededor de 400 filas y esta conversión lleva unos buenos 5 o 6 segundos.
Parece una tarea bastante común, así que estoy sorprendido de no ver a alguien aquí con una solución más eficaz.
* ¡¡Actualiza !! * Solo por las patadas, pensé que intentaría convertir utilizando LINQ en lugar de iterar a través de DataTable y agregar a mi lista. Lo siguiente es lo que hice:
List<MyObject> returnList = new List<MyObject>();
MyDataTable dtMyData = new MyTableAdapter().GetMyData();
returnLists = (from l in dtMyData
select new MyObject
{
Active = l.IsActive,
Email = l.Email,
//...
//About 40 more properties
//...
ZipCode = l.Zipcode
}).ToList();
El primer método (iterando a través de cada fila) tomó 5.3 segundos y el método con LINQ tomó 1.8 segundos.
Necesitarás iterar a través de los datarows y convertirlos en tus objetos en algún momento, pero podrías escribir una colección personalizada con un constructor que tome una colección de datarows y que convierta cada elemento en tu tipo de objeto cuando se solicite en lugar de todo en una vez.
Supongamos que define su colección personalizada que implementa IList<T>
. Podría tener dos campos internos: una referencia a la colección de datarows y una List<T>
. La List<T>
se inicializaría a la longitud de la colección de datarows pero con valores nulos.
Ahora en el indexador puede verificar si la List<T>
contiene un valor en ese índice y, si no, puede crear el objeto utilizando cualquier medio que sea útil y almacenarlo allí antes de devolverlo.
Esto pospondrá la creación de sus objetos hasta que se soliciten y usted solo creará los objetos que se solicitaron.
Sus objetos probablemente necesitarían que un constructor tomara un DataRow o necesitaría algún tipo de fábrica para crearlos, pero ese es otro tema.
Solo para proporcionar un poco más de facilidad de uso a la respuesta de Mark en una aplicación de consola simple:
class Program
{
static void Main(string[] args)
{
//define a DataTable obj
DataTable table = new DataTable
{
Columns = {
{"Foo", typeof(int)},
{"Bar", typeof(string)}
}
};
//populate it the DataTable
for (int i = 0; i < 3; i++)
{
table.Rows.Add(i, "Row " + i);
}
List<MyType> listWithTypedObjects= new List<MyType>(table.Rows.Count);
foreach (DataRow row in table.Rows)
{
listWithTypedObjects.Add(new MyType((int)row[0], (string)row[1]));
}
Console.WriteLine(" PRINTING THE POPULATED LIST ");
foreach (MyType objMyType in listWithTypedObjects)
{
Console.Write(" I have object of the type " + objMyType.ToString() + " => " );
Console.Write(" with Prop1OfTypeInt " + objMyType.Prop1OfTypeInt.ToString() + " , ");
Console.WriteLine(" with Prop1OfTypeInt " + objMyType.Prop2OfTypeString.ToString() + " ");
}
Console.WriteLine(" /n /n /n HIT A KEY TO EXIT THE PROGRAM ");
Console.ReadKey();
}
}
class MyType {
public int Prop1OfTypeInt { get; set; }
public string Prop2OfTypeString { get; set; }
/// <summary>
/// Note the order of the passed parameters is important !!!
/// </summary>
public MyType( int prop1OfTypeInt , string prop2OfTypeString)
{
this.Prop1OfTypeInt = prop1OfTypeInt;
this.Prop2OfTypeString = prop2OfTypeString;
}
}
¿Conoces la estructura del DataTable
y el objeto tipeado con anticipación? Puede usar un delegado para hacer la asignación. Si no lo hace (es decir, todo lo que sabe es un Type
y propiedades), hay formas de acelerar el acceso dinámico de miembros (como HyperDescriptor
).
De cualquier manera, considere un bloque iterador; de esa manera no tienes que almacenar los objetos por segunda vez; por supuesto, si solo está lidiando con cantidades pequeñas, esto no es un problema.
¿Puedes aclarar alguno de esos puntos? Puedo agregar muchos más detalles ...
En el más simple, ¿qué está mal con:
DataTable table = new DataTable {
Columns = {
{"Foo", typeof(int)},
{"Bar", typeof(string)}
}
};
for (int i = 0; i < 5000; i++) {
table.Rows.Add(i, "Row " + i);
}
List<MyType> data = new List<MyType>(table.Rows.Count);
foreach (DataRow row in table.Rows) {
data.Add(new MyType((int)row[0], (string)row[1]));
}
(los problemas en lo anterior podrían dirigir el enfoque correcto ...)
public partial class issuereceive_manageroffice_bal
{
public int issue_id{get;set;}
public string process{get;set;}
public DateTime issue_date{get;set;}
public TimeSpan issue_time{get;set;}
public string eg_no{get;set;}
public string lotno{get;set;}
public string clarity{get;set;}
public string sieves{get;set;}
public string shape{get;set;}
public double issue_carat{get;set;}
public int issue_pieces{get;set;}
public int receive_pieces{get;set;}
public double receive_carat{get;set;}
public int kp_pieces{get;set;}
public decimal kp_carat{get;set;}
public double loss{get;set;}
public string issue_manager{get;set;}
public string issue_by{get;set;}
public string receive_by{get;set;}
public int status{get;set;}
public DateTime receive_date{get;set;}
public string receive_time{get;set;}
public int factory_id{get;set;}
}
List<issuereceive_manageroffice_bal> issue_receive_list = new List<issuereceive_manageroffice_bal>();
issue_receive_list =
(from DataRow dr in DataTable.Rows
select new issuereceive_manageroffice_bal()
{
issue_id = 0,
issue_time = TimeSpan.Parse("0"),
receive_time = null,
shape = null,
process = dr["process"].ToString(),
issue_date = Convert.ToDateTime(dr["issue_date"]),
eg_no = dr["eg_no"].ToString(),
lotno = dr["lotno"].ToString(),
clarity = dr["clarity"].ToString(),
sieves = dr["sieves"].ToString(),
issue_carat = dr["issue_carat"].ToString() != "" ? double.Parse(dr["issue_carat"].ToString()) : 0,
issue_pieces = dr["issue_pieces"].ToString() != "" ? int.Parse(dr["issue_pieces"].ToString()) : 0,
receive_carat = dr["receive_carat"].ToString() != "" ? double.Parse(dr["receive_carat"].ToString()) : 0,
kp_pieces = dr["kp_pieces"].ToString() != "" ? int.Parse(dr["kp_pieces"].ToString()) : 0,
kp_carat = dr["kp_carat"].ToString() != "" ? decimal.Parse(dr["kp_carat"].ToString()) : 0,
loss = dr["loss"].ToString() != "" ? double.Parse(dr["loss"].ToString()) : 0,
issue_manager = dr["lotno"].ToString(),
issue_by = dr["issue_by"].ToString(),
receive_by = dr["receive_by"].ToString(),
status = dr["status"].ToString() != "" ? int.Parse(dr["status"].ToString()) : 0,
receive_date = Convert.ToDateTime(dr["receive_date"]),
factory_id = dr["factory_id"].ToString() != "" ? int.Parse(dr["factory_id"].ToString()) : 0,
}).ToList();