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
.