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.