Esta es la última entrega de esta zaga. En este artículo haré un resumen de lo investigado, las mejoras que hemos encontrado para mejorar el rendimiento de Lectura de Datos con ADO.NET, y haremos algunos gráficos comparativos que siempre ayudan a visualizar mejor la comparación.
Este artículo es una continuación de los artículos anteriores:
Anti Prácticas .NET: Lectura de Datos con ADO.NET
Anti Prácticas .NET: Lectura de Datos con ADO.NET II
Presentación del escenario
Este es el contexto en el que estoy haciendo las mediciones:
Una aplicación Windows Forms, que utiliza 4 mecanismos para recuperar datos “de solo lectura” de la base de datos AdvertureWorks alojada en SQL Server 2005:
- DataReader cargado en una lista genérica de objetos de entidad
- DataSet
- DataTable
- Dataset tipificado creado con el asistente de Visual Studio 2005
Aquí subrayo “solo lectura” porque, justamente solo quiero recuperar los datos, y no hacer ninguna operación sobre ellos.
En realidad las 3 primeras técnicas fueron descriptas en los artículos anteriores. En este se agrega la carga del DataSet tipificado. Por lo menos mi experiencia me dice que los Asistentes crean código no muy optimizado que digamos. Así que llamemos de nuevo al “Cazador de Mitos .NET”, que nos dirá:
Vamos a la caza del mito: “El código generado por un asistente no desempeña un buen rendimiento”
El Código
La versión completa del código podrás bajarla de aquí. De todas formas démosle un vistazo:
Esta es la sentencia sql a ejecutar en la base de datos AdventureWorks:
Select HumanResources.Employee.EmployeeID, Person.Contact.FirstName
Person.Contact.MiddleName, Person.Contact.LastName,
HumanResources.Employee.Title, HumanResources.Employee.BirthDate,
Person.Address.AddressLine1, Person.Address.AddressLine2,
Person.Address.City, Person.Address.PostalCode, Person.Contact.EmailAddress,
Person.Contact.Phone, HumanResources.Employee.MaritalStatus, HumanResources.Employee.Gender
FROM HumanResources.Employee
INNER JOIN Person.Contact
ON HumanResources.Employee.ContactID = Person.Contact.ContactID
INNER JOIN HumanResources.EmployeeAddress
ON HumanResources.Employee.EmployeeID = HumanResources.EmployeeAddress.EmployeeID
INNER JOIN Person.Address
ON HumanResources.EmployeeAddress.AddressID = Person.Address.AddressID
AND HumanResources.EmployeeAddress.AddressID = Person.Address.AddressID
El DataSet tipificado fue creado arrastrando la consulta SQL sobre la superficie de diseño del DataSet, lo único que escribí fue las siguientes líneas para cargar el DataSet tipificado:
private void button1_Click(object sender, EventArgs e)
{
DsEmployees.GetEmployeesDataTable dsEmployees;
DsEmployeesTableAdapters.GetEmployeesTableAdapter da = new DsEmployeesTableAdapters.GetEmployeesTableAdapter();
dsEmployees = da.GetData();
}
Lectura del DataSet tipificado
Vamos a medir el tiempo que consume la carga del DataSet tipificado. Para ello recordemos que la consulta devuelve 290 regitros y que la ejecuto 10 veces.

El tiempo consumido es de 7618,5 ms. Recuerda que la lectura anterior del DataSet era 2386 ms, para el DataTable 2369 ms y para el List<> 1883 ms.Creo que realmente no vale la pena hacer un estudio para saber internamente dónde está el mayor consumo, puesto que el uso de asistentes nunca fue una práctica recomendada.
¡Mito cazado! J. El código generado por un asistente no desempeña un buen rendimiento.
Comparación de resultados
Veamos una serie de gráficos que resumen las lecturas realizadas. Tomé lecturas de 290 registros, 10 registros (que es el típico caso del tamaño de una página cuando se realiza paginación) y 1 registro.

Fig1: Tabla Comparativa

Fig2: Lectura de 290 registros

Fig3: Lectura de 10 y 1 registro.
Estos gráficos muestran claramente que la técnica más rápida es la carga de una lista genérica List<> de objetos de Entidades; técnica utilizada en capas de acceso a datos DALs (Data Access Layers), y la encontraremos por varios sitios como mejor práctica.
Resumen de las mejoras realizadas
La primera mejora realizada fue respecto de la carga de un DataTable. En ese caso habíamos detectado que la carga de un DataTable StandAlone era más lento que la de un DataSet. Para mejorar el tiempo habíamos reemplazado la llamada DataTable.Load() por el uso de un DataAdapter.Fill(dataTable). El DataAdapter está optimizado para solo lectura de manera predeterminada.
La segunda mejora tiene que ver con la carga del List<>. Allí habíamos detectado que llamadas a GetOrdinal() dentro del bucle que podían obviarse, justamente obteniendo el ordinal de la columna antes de lanzar el bucle. Y la otra mejora era obtener todos los valores de la columnas de una fila con el método GetValues(vector), y luego tomar los datos del vector.
LINQ to SQL
Me hubiese gustado poder comparar en esta serie de artículos el uso de LINQ to SQL y Entity Data Framework, pero no sería muy preciso hacerlo con un código beta. Como decía no podemos comparar, pero si ver que es lo más optimo dentro de LINQ to SQL. Para ello, te recomiendo un artículo del maestro Daniel Seara.
Conclusión
Hemos comprobado que el uso correcto de las técnicas de acceso a datos en ADO.NET nos permite lograr un mayor rendimiento en nuestras aplicaciones. También hemos aprendido algo de cómo funciona internamente ADO.NET, ejercicio que nos va a servir para tomar buenas decisiones al momento de elegir nuestra estrategia de acceso a datos.
La próxima entrega tendrá que ver con el consumo de memoria que estás técnicas hacen.