Streaming de fragmentos XML desde un objeto XmlReader (LINQ to XML)
Cuando deba procesar archivos XML grandes quizás no sea factible cargar la totalidad del árbol XML en memoria. En este artículo se muestra cómo transmitir fragmentos mediante XmlReaderen C# y Visual Basic.
Una de las formas más efectivas de usar XmlReader para leer objetos XElement es escribir un método de eje personalizado propio. Un método de eje suele devolver una recopilación como IEnumerable<T> de XElement, tal y como se muestra en el ejemplo de este tema. En el método de eje personalizado, tras crear el fragmento XML llamando al método ReadFrom, devuelva la recopilación usando yield return
. Esto proporciona semántica de ejecución aplazada al método de eje personalizado.
Cuando crea un árbol XML de un objeto XmlReader, XmlReader debe estar posicionado en un elemento. El método ReadFrom no vuelve hasta que ha leído la etiqueta de cierre del elemento.
Si desea crear un árbol parcial, puede crear una instancia de un XmlReader, colocar el lector en el nodo que desea convertir a un árbol XElement y después crear el objeto XElement.
El artículo Hacer streaming de fragmentos XML con acceso a la información del encabezado contiene información y un ejemplo sobre cómo hacer streaming de un documento más complejo.
El tema Procedimiento para realizar una transformación de streaming de documentos XML grandes contiene un ejemplo del uso de LINQ to XML para transformar documentos XML extremadamente grandes manteniendo una superficie de memoria pequeña.
Ejemplo: Crear un método de eje personalizado
Este ejemplo crea un método de eje personalizado. Puede consultarlo usando una consulta de LINQ. El método de eje personalizado StreamRootChildDoc
puede leer un documento que tiene un elemento Child
de repetición.
using System.Xml;
using System.Xml.Linq;
static IEnumerable<XElement> StreamRootChildDoc(StringReader stringReader)
{
using XmlReader reader = XmlReader.Create(stringReader);
reader.MoveToContent();
// Parse the file and display each of the nodes.
while (true)
{
// If the current node is an element and named "Child"
if (reader.NodeType == XmlNodeType.Element && reader.Name == "Child")
{
// Get the current node and advance the reader to the next
if (XNode.ReadFrom(reader) is XElement el)
yield return el;
}
else if (!reader.Read())
break;
}
}
string markup = """
<Root>
<Child Key="01">
<GrandChild>aaa</GrandChild>
</Child>
<Child Key="02">
<GrandChild>bbb</GrandChild>
</Child>
<Child Key="03">
<GrandChild>ccc</GrandChild>
</Child>
</Root>
""";
IEnumerable<string> grandChildData =
from el in StreamRootChildDoc(new StringReader(markup))
where (int)el.Attribute("Key") > 1
select (string)el.Element("GrandChild");
foreach (string str in grandChildData)
Console.WriteLine(str);
Imports System.Xml
Module Module1
Public Iterator Function StreamRootChildDoc(stringReader As IO.StringReader) As IEnumerable(Of XElement)
Using reader As XmlReader = XmlReader.Create(stringReader)
reader.MoveToContent()
' Parse the file and display each of the nodes.
While True
' If the current node is an element and named "Child"
If reader.NodeType = XmlNodeType.Element And reader.Name = "Child" Then
' Get the current node and advance the reader to the next
Dim el As XElement = TryCast(XNode.ReadFrom(reader), XElement)
If (el IsNot Nothing) Then
Yield el
End If
ElseIf Not reader.Read() Then
Exit While
End If
End While
End Using
End Function
Sub Main()
Dim markup = "<Root>
<Child Key=""01"">
<GrandChild>aaa</GrandChild>
</Child>
<Child Key=""02"">
<GrandChild>bbb</GrandChild>
</Child>
<Child Key=""03"">
<GrandChild>ccc</GrandChild>
</Child>
</Root>"
Dim grandChildData =
From el In StreamRootChildDoc(New IO.StringReader(markup))
Where CInt(el.@Key) > 1
Select el.<GrandChild>.Value
For Each s In grandChildData
Console.WriteLine(s)
Next
End Sub
End Module
Este ejemplo produce el siguiente resultado:
bbb
ccc
La técnica utilizada en este ejemplo mantiene una superficie de memoria pequeña incluso para millones de elementos Child
.