Anti Prácticas .NET: Lectura de Datos con ADO.NET III

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.

11 thoughts on “Anti Prácticas .NET: Lectura de Datos con ADO.NET III

  1. Muy interesante las comparaciones en cuanto a velocidad, pero ¿que hay respecto a la eficiencia en el uso de la memoria?. ¿Cuales serían los escenarios idoneos para obtener la mejor relacion velocidad – consumo de memoria?

  2. Hola.

    De una parte me llama la atención el comentario relacionado con el código de bajo rendimiento de los Datasets fuertemente tipados generado por los asistentes. Por qué Microsoft haría eso? Por ahi, también he leido acerca de las ventajas de usar Datasets fuertemente tipados, asi que parece que hay una contradicción. Asi que me gustaría saber cuál sería la mejor técnica para usar Datasets fuertemente tipificados?

    Un abrazo.

  3. Dev, entiendo tu planteo. Algo que debes tener en cuenta es el contexto en el cual estará corriendo tu código y las exigencias del negocio.
    Poner al descubierto el rendimiento de cual o tal técnica no la invalida, sino que, conocer su funcionamiento interno, ayuda al momento de tomar una decisión de uso.
    Todos conocemos las ventajas de usar un DataSet tipificado, y las herramientas de VS para su generación, lo cual aumenta considerablemente la productividad. Si el rendimiento no es un requerimiento no funcional de tu aplicación, no está mal su elección. Pero si el buen desempeño de tu aplicación es escencial, debería entonces realizar una prueba de concepto, tomando como ayuda este artículo.

    Gracias

  4. Como puedo usar esta parte del codigo si estoy usando 3 capas?
    Assembly.GetExecutingAssembly().GetManifestResourceStream(“Walzer.Antipracticas.Consulta.sql”)

    No puedo leer la consulta y no se en que capa deba ponerla

  5. Olvide mencionar que es una aplicacion web en la que quiero acceder a la consulta que esta en un archivo llamado Consulta.sql

  6. Carlos te amooooo!!! hasme un hijo ahora!!!… realmente muy bueno este articulo sacada de sombrero!! que sigan los éxitos!.

Leave a Reply

Your email address will not be published. Required fields are marked *