Partilhar via


Como transmitir fragmentos XML de um XmlReader (LINQ to XML)

Quando você tem que processar arquivos XML grandes, pode não ser viável carregar toda a árvore XML na memória. Este artigo mostra como transmitir fragmentos usando um XmlReader em C# e Visual Basic.

Uma das maneiras mais eficazes de usar um XmlReader para ler XElement objetos é escrever seu próprio método de eixo personalizado. Um método de eixo normalmente retorna uma coleção como IEnumerable<T> de XElement, conforme mostrado no exemplo deste artigo. No método de eixo personalizado, depois de criar o fragmento XML chamando o ReadFrom método, retorne a coleção usando yield return. Isso fornece semântica de execução adiada para seu método de eixo personalizado.

Quando você cria uma árvore XML a partir de um XmlReader objeto, o deve ser posicionado XmlReader em um elemento . O ReadFrom método não retorna até que ele tenha lido a marca close do elemento.

Se quiser criar uma árvore parcial, você pode instanciar um XmlReader, posicionar o leitor no nó que deseja converter em uma XElement árvore e, em seguida, criar o XElement objeto.

O artigo Como transmitir fragmentos XML com acesso a informações de cabeçalho contém informações sobre o streaming de um documento mais complexo.

O artigo How to perform streaming transform of large XML documents contém um exemplo de como usar o LINQ to XML para transformar documentos XML extremamente grandes, mantendo um pequeno espaço de memória.

Exemplo: Criar um método de eixo personalizado

Este exemplo cria um método de eixo personalizado. Você pode consultá-lo usando uma consulta LINQ. O método StreamRootChildDoc de eixo personalizado pode ler um documento que tem um elemento de Child repetição.

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 exemplo produz a seguinte saída:

bbb
ccc

A técnica usada neste exemplo mantém uma pequena pegada de memória mesmo para milhões de Child elementos.

Consulte também