
Después de haber aprendido lo que es LINQ y cómo funciona, es obligado ver si, además de simplificar el código, mejora la eficiencia de nuestro proyecto.
Me he decidido por probar la lectura de archivos XML por parte de LINQ y determinar quien tiene mayor velocidad de lectura, la librerÃa de acceso a XML de .Net o la nueva tecnologÃa: LINQ. Para ello, he creado un XML con 100.000 registros que simula una tabla de alumnos y realizaré consultas a dicho XML.
Éstas son las consultas que he decidido realizar utilizando ambos sistemas:
- Prueba 1: Extraer todos los alumnos que se llamen Jorge.
- Prueba 2: Contar cuantos alumnos han aprobado.
- Prueba 3: De entre los alumnos que se llaman Jorge, ¿cuántos han suspendido?
- Prueba 4: De entre los alumnos que han sacado más de un 8, ¿cual es el nombre más predominante?
- Prueba 5: Extraer sólo los nombres distintos de los alumnos y ordenarlos..
Y tras lanzar las mismas aquà están, en segundos, los tiempos que ha tardado cada método en cada una de las consultas anteriores:
| Prueba | XML Reader | LINQ |
| 1 | 0,301934 | 0,000036 |
| 2 | 0,589991 | 0,090168 |
| 3 | 0,390582 | 0,054944 |
| 4 | 0,432305 | 0,091441 |
| 5 | 0,281885 | 0,042886 |
Se puede apreciar claramente la diferencia en velocidades de acceso, siendo el XML Reader entre 7 y 8 veces más lento que LINQ. Pero además, las consultas en LINQ me han ocupado entre 1 y 3 lÃneas (y porque he querido dividirlo en lÃneas) mientras que las realizadas con el antiguo método ocupaban entre 10 y 30 lÃneas.
Por todo esto, LINQ parece batir al XML Reader tanto en velocidad de acceso como en simplificación de código, lo que reduce errores y costes de producción o mantenimiento.
Sé que existen otros métodos para acceder a un XML en .Net (la librerÃa de XML ofrece otras opciones), pero yo me he decantado por ésta, quizás alguna otra resultase más eficiente, pero tras realizar varias pruebas con LINQ, realmente me ha convencido como tecnologÃa de acceso a conjuntos de datos.
Por último, dejo aquà el código fuente utilizado, por si a alguien le pica la curiosidad o he metido la pata y alguien puede corregirme:
Private Sub TestVelocidad1()
Dim rutaArchivo As String = "C:/test/xmlAlumnos100.xml"
Dim metodoReader As System.Xml.XmlTextReader
Dim metodoLINQ As System.Xml.Linq.XDocument = System.Xml.Linq.XDocument.Load(rutaArchivo)
Dim lstAlumnos As New Collections.Generic.List(Of Alumno)
Dim lstNombres As New Collections.Generic.List(Of String)
Dim oAlumno As Alumno = Nothing
Dim notaAux As Byte = 0
Dim cantAprobados As Integer = 0
Dim cantSuspendidos As Integer = 0
Page.Trace.IsEnabled = True
'Prueba 1: Extraer todos los alumnos que se llamen Jorge.
Trace.Warn("Empieza la prueba 1")
Dim alumnos = From alum In metodoLINQ.Descendants("alumno") _
Where alum.Attribute("nombre") IsNot Nothing AndAlso alum.Attribute("nombre").Value = "Jorge" _
Select Nombre = alum.Attribute("nombre").Value, Nota = alum.Attribute("nota").Value
Trace.Warn("Fin prueba 1 con LINQ")
metodoReader = New System.Xml.XmlTextReader(rutaArchivo)
Do While metodoReader.Read
If metodoReader.NodeType = System.Xml.XmlNodeType.Element AndAlso metodoReader.Name = "alumno" AndAlso metodoReader.HasAttributes Then
oAlumno = New Alumno
While metodoReader.MoveToNextAttribute
If metodoReader.Name = "nombre" Then
oAlumno.Nombre = metodoReader.Value
If oAlumno.Nombre <> "Jorge" Then Exit While
ElseIf metodoReader.Name = "nota" Then
Byte.TryParse(metodoReader.Value, oAlumno.Nota)
End If
End While
If oAlumno.Nombre = "Jorge" Then lstAlumnos.Add(oAlumno)
End If
Loop
Trace.Warn("Fin prueba 1 con Reader")
'Prueba 2: Contar cuantos alumnos han aprobado.
Trace.Warn("Empieza la prueba 2")
cantAprobados = (From alum In metodoLINQ.Descendants("alumno") Where alum.Attribute("nota") IsNot Nothing AndAlso Integer.Parse(alum.Attribute("nota").Value) > 4).Count
Trace.Warn("Fin prueba 2 con LINQ")
metodoReader = New System.Xml.XmlTextReader(rutaArchivo)
cantAprobados = 0
Do While metodoReader.Read
If metodoReader.NodeType = System.Xml.XmlNodeType.Element AndAlso metodoReader.Name = "alumno" AndAlso metodoReader.HasAttributes Then
notaAux = 0
While metodoReader.MoveToNextAttribute
If metodoReader.Name = "nota" Then
Byte.TryParse(metodoReader.Value, notaAux)
If notaAux > 4 Then cantAprobados += 1
End If
End While
End If
Loop
Trace.Warn("Fin prueba 2 con Reader")
'Prueba 3: De entre los alumnos que se llaman Jorge, ¿cuántos han suspendido?
Trace.Warn("Empieza la prueba 3")
cantSuspendidos = (From alum In metodoLINQ.Descendants("alumno") _
Where alum.Attribute("nombre") IsNot Nothing AndAlso alum.Attribute("nombre").Value = "Jorge" _
Where alum.Attribute("nota") IsNot Nothing AndAlso Integer.Parse(alum.Attribute("nota").Value) < 5).Count
Trace.Warn("Fin prueba 3 con LINQ")
metodoReader = New System.Xml.XmlTextReader(rutaArchivo)
cantSuspendidos = 0
oAlumno = New Alumno
Do While metodoReader.Read
If metodoReader.NodeType = System.Xml.XmlNodeType.Element AndAlso metodoReader.Name = "alumno" AndAlso metodoReader.HasAttributes Then
notaAux = 0 : oAlumno.Nombre = "" : oAlumno.Nota = 0
While metodoReader.MoveToNextAttribute
If metodoReader.Name = "nota" Then Byte.TryParse(metodoReader.Value, oAlumno.Nota)
If metodoReader.Name = "nombre" Then oAlumno.Nombre = metodoReader.Value
End While
If oAlumno.Nombre = "Jorge" And oAlumno.Nota < 5 Then cantSuspendidos += 1
End If
Loop
Trace.Warn("Fin prueba 3 con Reader")
'Prueba 4: De entre los alumnos que han sacado más de un 8, ¿cual es el nombre más predominante?
Dim NombreMasEmpollon As String = ""
Trace.Warn("Empieza la prueba 4")
Dim empollon As Generic.List(Of String) = (From n In (From alum In metodoLINQ.Descendants("alumno") _
Where (alum.Attribute("nota") IsNot Nothing AndAlso Integer.Parse(alum.Attribute("nota").Value) >
_
Group alum By nombre = alum.Attribute("nombre").Value Into cantNombres = Count() _
Select nombre, cantNombres Order By cantNombres Descending Order By cantNombres Descending Take 1) Select n.nombre).ToList
NombreMasEmpollon = empollon.Item(0).ToString
Trace.Warn("Fin prueba 4 con LINQ")
metodoReader = New System.Xml.XmlTextReader(rutaArchivo)
Dim klstNombres As New Generic.SortedList(Of String, Integer)
oAlumno = New Alumno
Do While metodoReader.Read
If metodoReader.NodeType = System.Xml.XmlNodeType.Element AndAlso metodoReader.Name = "alumno" AndAlso metodoReader.HasAttributes Then
oAlumno.Nombre = "" : oAlumno.Nota = 0
While metodoReader.MoveToNextAttribute
If metodoReader.Name = "nota" Then Byte.TryParse(metodoReader.Value, oAlumno.Nota)
If metodoReader.Name = "nombre" Then oAlumno.Nombre = metodoReader.Value
If oAlumno.Nota > 0 And oAlumno.Nombre.Length > 0 Then
If oAlumno.Nota > 8 Then
If klstNombres.Keys.IndexOf(oAlumno.Nombre) < 0 Then
klstNombres.Add(oAlumno.Nombre, 1)
Else
klstNombres.Item(oAlumno.Nombre) = klstNombres.Item(oAlumno.Nombre) + 1
End If
End If
Exit While
End If
End While
End If
Loop
NombreMasEmpollon = klstNombres.Keys(0)
For Each n As String In klstNombres.Keys
If klstNombres(n) > klstNombres(NombreMasEmpollon) Then
NombreMasEmpollon = n
End If
Next
Trace.Warn("Fin prueba 4 con Reader")
'Prueba 5: Extraer sólo los nombres distintos de los alumnos y ordenarlos.
Trace.Warn("Empieza la prueba 5")
Dim nombres As String() = (From alum In metodoLINQ.Descendants("alumno") Select n = alum.Attribute("nombre").Value Distinct Order By n.ToString).ToArray
Trace.Warn("Fin prueba 5 con LINQ")
metodoReader = New System.Xml.XmlTextReader(rutaArchivo)
lstNombres.Clear()
Do While metodoReader.Read
If metodoReader.NodeType = System.Xml.XmlNodeType.Element AndAlso metodoReader.Name = "alumno" AndAlso metodoReader.HasAttributes Then
While metodoReader.MoveToNextAttribute
If metodoReader.Name = "nombre" Then
If lstNombres.IndexOf(metodoReader.Value) < 0 Then
lstNombres.Add(metodoReader.Value)
End If
Exit While
End If
End While
End If
Loop
lstNombres.Sort()
Trace.Warn("Fin prueba 5 con Reader")
End Sub
Public Class Alumno
#Region "Variables"
Private _nombre As String
Private _nota As Byte
#End Region
#Region "Propiedades"
Public Property Nombre() As String
Get
Return _nombre
End Get
Set(ByVal value As String)
_nombre = value
End Set
End Property
Public Property Nota() As Byte
Get
Return _nota
End Get
Set(ByVal value As Byte)
_nota = value
End Set
End Property
#End Region
End Class



Muy buen analisis entre las dos opciones. !!!