Compartilhar via


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

Quando você tem que processa grandes arquivos XML, talvez não seja possível carregar a árvore inteira XML na memória. Este artigo mostra como transmitir fragmentos usando um XmlReader em C# e Visual Basic.

Um dos modos de efetivas usar XmlReader para ler objetos de XElement é escrever seu próprio método personalizado do eixo. Um método de eixo normalmente retorna uma coleção, como IEnumerable<T> de XElement, conforme mostrado no exemplo neste artigo. No método personalizado do eixo, depois de criar o fragmento XML chamando o método ReadFrom , retornar a coleção usando yield return. Isso fornece a semântica de execução adiada ao método personalizado do eixo.

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

Se você desejar criar uma árvore parcial, você pode criar uma instância XmlReader, posiciona o leitor no nó que você deseja converter a XElement uma árvore e em seguida, cria o objeto de XElement .

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

O artigo Como executar a transformação de streaming de grandes documentos XML contém um exemplo do uso de LINQ to XML para transformar documentos XML muito grandes, mantendo um volume de memória pequeno.

Exemplo: Criar um método de eixo personalizado

Este exemplo cria um método personalizado do eixo. Você pode consultá-lo usando uma consulta LINQ. O método de eixo personalizado StreamRootChildDoc pode ler um documento que possui um elemento Child repetido.

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

Esse exemplo gera a saída a seguir:

bbb
ccc

A técnica usada neste exemplo mantém um pequeno volume de memória mesmo para milhões de elementos Child.

Confira também