Como transmitir fragmentos XML com acesso a informações de cabeçalho (LINQ to XML)
Às vezes você precisará ler arbitrariamente grandes arquivos XML, e escreve seu aplicativo para que os vestígio de memória do aplicativo seja previsível. Se você tentar preencher uma árvore XML com um grande arquivo XML, seu uso de memória será proporcionalmente o tamanho do arquivo que é, excessivo. Portanto, você deve usar uma técnica de streaming em vez disso.
Uma opção é escrever seu aplicativo usando XmlReader. No entanto, talvez você queira usar o LINQ para consultar a árvore XML. Se for o caso, você poderá escrever seu próprio método personalizado do eixo. Para mais informações, confira Como gravar um método do eixo LINQ to XML.
Para escrever seu próprio método do eixo, você escreve um pequeno método que usa XmlReader para ler nós até que atingiu um dos nós em que você está interessado. O método chama em ReadFrom, que lê de XmlReader e cria uma instância de um fragmento XML. Resulta em cada fragmento com yield return
o método que está enumerando o método personalizado do eixo. Você pode escrever consultas LINQ no método personalizado do eixo.
As técnicas de streaming são melhor aplicadas em situações onde você precisa processar o documento de origem apenas uma vez e você pode processar os elementos na ordem do documento. Determinados operadores de consulta padrão, como OrderBy, iteram sua origem, coletam todos os dados, classificam e, em seguida, geram finalmente o primeiro item na sequência. Se você usa um operador de consulta que materialize sua origem antes de como o primeiro item, não retém um volume de memória pequeno.
Exemplo: implementar e usar um método personalizado do eixo que passa fragmentos XML do arquivo especificado por um URI
Às vezes o problema é apenas um pouco mais interessante. No documento XML a seguir, o consumidor do método personalizado do eixo também precisa saber o nome do cliente que cada item pertence.
<?xml version="1.0" encoding="utf-8" ?>
<Root>
<Customer>
<Name>A. Datum Corporation</Name>
<Item>
<Key>0001</Key>
</Item>
<Item>
<Key>0002</Key>
</Item>
<Item>
<Key>0003</Key>
</Item>
<Item>
<Key>0004</Key>
</Item>
</Customer>
<Customer>
<Name>Fabrikam, Inc.</Name>
<Item>
<Key>0005</Key>
</Item>
<Item>
<Key>0006</Key>
</Item>
<Item>
<Key>0007</Key>
</Item>
<Item>
<Key>0008</Key>
</Item>
</Customer>
<Customer>
<Name>Southridge Video</Name>
<Item>
<Key>0009</Key>
</Item>
<Item>
<Key>0010</Key>
</Item>
</Customer>
</Root>
A abordagem que este exemplo usa é inspecione também para essas informações de cabeçalho, salvar informações de cabeçalho, e então cria uma árvore XML pequena que contém informações de cabeçalho e o detalhe que você está enumerando. O método do eixo resulta nessa nova árvore, pequena XML. A consulta possui o acesso a informações de cabeçalho bem como para informações detalhadas.
Essa abordagem tem uma pegada pequena de memória. Conforme cada fragmento XML de detalhe é gerado, nenhuma referência é mantida para o fragmento anterior, e ele está disponível para coleta de lixo. Essa técnica cria uma breve muitos objetos na heap.
O exemplo a seguir mostra como implementar e usar um método personalizado do eixo que passa fragmentos XML do arquivo especificado por um URI. Esse eixo personalizado é escrito para que espera um documento que tem Customer
, Name
, e os elementos de Item
, e que esses elementos serão organizados como no documento anterior de Source.xml
. É uma implementação simplista. Uma implementação mais robusta seria preparada para analisar um documento válido.
static IEnumerable<XElement> StreamCustomerItem(string uri)
{
using (XmlReader reader = XmlReader.Create(uri))
{
XElement name = null;
XElement item = null;
reader.MoveToContent();
// Parse the file, save header information when encountered, and yield the
// Item XElement objects as they're created.
// loop through Customer elements
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element
&& reader.Name == "Customer")
{
// move to Name element
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element &&
reader.Name == "Name")
{
name = XElement.ReadFrom(reader) as XElement;
break;
}
}
// Loop through Item elements
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.EndElement)
break;
if (reader.NodeType == XmlNodeType.Element
&& reader.Name == "Item")
{
item = XElement.ReadFrom(reader) as XElement;
if (item != null) {
XElement tempRoot = new XElement("Root",
new XElement(name)
);
tempRoot.Add(item);
yield return item;
}
}
}
}
}
}
}
static void Main(string[] args)
{
XElement xmlTree = new XElement("Root",
from el in StreamCustomerItem("Source.xml")
where (int)el.Element("Key") >= 3 && (int)el.Element("Key") <= 7
select new XElement("Item",
new XElement("Customer", (string)el.Parent.Element("Name")),
new XElement(el.Element("Key"))
)
);
Console.WriteLine(xmlTree);
}
Module Module1
Sub Main()
Dim xmlTree = <Root>
<%=
From el In New StreamCustomerItem("Source.xml")
Let itemKey = CInt(el.<Key>.Value)
Where itemKey >= 3 AndAlso itemKey <= 7
Select <Item>
<Customer><%= el.Parent.<Name>.Value %></Customer>
<%= el.<Key> %>
</Item>
%>
</Root>
Console.WriteLine(xmlTree)
End Sub
End Module
Public Class StreamCustomerItem
Implements IEnumerable(Of XElement)
Private _uri As String
Public Sub New(ByVal uri As String)
_uri = uri
End Sub
Public Function GetEnumerator() As IEnumerator(Of XElement) Implements IEnumerable(Of XElement).GetEnumerator
Return New StreamCustomerItemEnumerator(_uri)
End Function
Public Function GetEnumerator1() As IEnumerator Implements IEnumerable.GetEnumerator
Return Me.GetEnumerator()
End Function
End Class
Public Class StreamCustomerItemEnumerator
Implements IEnumerator(Of XElement)
Private _current As XElement
Private _customerName As String
Private _reader As Xml.XmlReader
Private _uri As String
Public Sub New(ByVal uri As String)
_uri = uri
_reader = Xml.XmlReader.Create(_uri)
_reader.MoveToContent()
End Sub
Public ReadOnly Property Current As XElement Implements IEnumerator(Of XElement).Current
Get
Return _current
End Get
End Property
Public ReadOnly Property Current1 As Object Implements IEnumerator.Current
Get
Return Me.Current
End Get
End Property
Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
Dim item As XElement
Dim name As XElement
' Parse the file, save header information when encountered, and return the
' current Item XElement.
' loop through Customer elements
While _reader.Read()
If _reader.NodeType = Xml.XmlNodeType.Element Then
Select Case _reader.Name
Case "Customer"
' move to Name element
While _reader.Read()
If _reader.NodeType = Xml.XmlNodeType.Element AndAlso
_reader.Name = "Name" Then
name = TryCast(XElement.ReadFrom(_reader), XElement)
_customerName = If(name IsNot Nothing, name.Value, "")
Exit While
End If
End While
Case "Item"
item = TryCast(XElement.ReadFrom(_reader), XElement)
Dim tempRoot = <Root>
<Name><%= _customerName %></Name>
<%= item %>
</Root>
_current = item
Return True
End Select
End If
End While
Return False
End Function
Public Sub Reset() Implements IEnumerator.Reset
_reader = Xml.XmlReader.Create(_uri)
_reader.MoveToContent()
End Sub
#Region "IDisposable Support"
Private disposedValue As Boolean ' To detect redundant calls
' IDisposable
Protected Overridable Sub Dispose(ByVal disposing As Boolean)
If Not Me.disposedValue Then
If disposing Then
_reader.Close()
End If
End If
Me.disposedValue = True
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
#End Region
End Class
Este código produz a seguinte saída:
<Root>
<Item>
<Customer>A. Datum Corporation</Customer>
<Key>0003</Key>
</Item>
<Item>
<Customer>A. Datum Corporation</Customer>
<Key>0004</Key>
</Item>
<Item>
<Customer>Fabrikam, Inc.</Customer>
<Key>0005</Key>
</Item>
<Item>
<Customer>Fabrikam, Inc.</Customer>
<Key>0006</Key>
</Item>
<Item>
<Customer>Fabrikam, Inc.</Customer>
<Key>0007</Key>
</Item>
</Root>