Compartir a través de


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.

Consulte también