Como transmitir fragmentos XML com acesso a informações de cabeçalho (LINQ to XML)
Às vezes, você tem que ler arquivos XML arbitrariamente grandes e escrever seu aplicativo para que o espaço ocupado pela memória do aplicativo seja previsível. Se você tentar preencher uma árvore XML com um arquivo XML grande, seu uso de memória será proporcional ao tamanho do arquivo, ou seja, excessivo. Portanto, você deve usar uma técnica de streaming.
Uma opção é escrever seu aplicativo usando XmlReadero . No entanto, convém usar o LINQ para consultar a árvore XML. Em caso afirmativo, você pode escrever seu próprio método de eixo personalizado. Para obter mais informações, consulte Como escrever um método de eixo LINQ to XML.
Para escrever seu próprio método de eixo, você escreve um pequeno método que usa o XmlReader para ler nós até chegar a um dos nós nos quais você está interessado. Em seguida, o método chama ReadFrom, que lê a partir do e instancia um fragmento XmlReader XML. Em seguida, ele produz cada fragmento até yield return
o método que está enumerando seu método de eixo personalizado. Em seguida, você pode escrever consultas LINQ em seu método de eixo personalizado.
As técnicas de streaming são melhor aplicadas em situações em que você precisa processar o documento de origem apenas uma vez e pode processar os elementos na ordem do documento. Certos operadores de consulta padrão, como OrderBy, iteram sua fonte, coletam todos os dados, classificam-nos e, finalmente, produzem o primeiro item na sequência. Se você usar um operador de consulta que materialize sua origem antes de produzir o primeiro item, não manterá um pequeno espaço de memória.
Exemplo: implementar e usar um método de eixo personalizado que transmite fragmentos XML do arquivo especificado por um URI
Às vezes, o problema fica um pouco mais interessante. No documento XML a seguir, o consumidor do seu método de eixo personalizado também precisa saber o nome do cliente ao qual 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 adotada neste exemplo é também observar essas informações de cabeçalho, salvar as informações de cabeçalho e, em seguida, criar uma pequena árvore XML que contenha as informações de cabeçalho e os detalhes que você está enumerando. Em seguida, o método axis produz essa nova e pequena árvore XML. Em seguida, a consulta tem acesso às informações do cabeçalho, bem como às informações detalhadas.
Esta abordagem tem uma pequena pegada de memória. À medida que cada fragmento XML de detalhe é produzido, nenhuma referência é mantida ao fragmento anterior e ele fica disponível para coleta de lixo. Esta técnica cria muitos objetos de curta duração na pilha.
O exemplo a seguir mostra como implementar e usar um método de eixo personalizado que transmite fragmentos XML do arquivo especificado pelo URI. Este eixo personalizado é escrito de tal forma que espera um documento que tenha Customer
, Name
e Item
elementos, e que esses elementos serão organizados como no documento acima Source.xml
. É uma implementação simplista. Uma implementação mais robusta estaria preparada para analisar um documento inválido.
static IEnumerable<XElement> StreamCustomerItem(string uri)
{
using XmlReader reader = XmlReader.Create(uri);
reader.MoveToContent();
// Parse the file, save header information when encountered, and yield the
// Item XElement objects as they're created.
// Loop through Customer elements
do
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "Customer")
{
// Move to Name element
XElement? name = null;
do
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "Name")
{
name = XNode.ReadFrom(reader) as XElement;
break;
}
}
while (reader.Read());
// Loop through Item elements
while (reader.NodeType != XmlNodeType.EndElement)
{
if (reader.NodeType == XmlNodeType.Element && reader.Name == "Item")
{
if (XNode.ReadFrom(reader) is XElement item && name != null)
{
XElement tempRoot = new XElement("Root",
new XElement(name),
item
);
yield return item;
}
}
else if (!reader.Read())
break;
}
}
}
while (reader.Read());
}
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);
}
Imports System.Xml
Module Module1
Public Iterator Function StreamCustomerItem(uri As String) As IEnumerable(Of XElement)
Using reader As XmlReader = XmlReader.Create(uri)
reader.MoveToContent()
' Parse the file, save header information when encountered, And yield the
' Item XElement objects as they're created.
' Loop through Customer elements
Do
If reader.NodeType = XmlNodeType.Element And reader.Name = "Customer" Then
' Move to Name element
Dim name As XElement = Nothing
Do
If reader.NodeType = XmlNodeType.Element And reader.Name = "Name" Then
name = TryCast(XNode.ReadFrom(reader), XElement)
Exit Do
End If
Loop While reader.Read()
' Loop through Item elements
While reader.NodeType <> XmlNodeType.EndElement
If reader.NodeType = XmlNodeType.Element And reader.Name = "Item" Then
Dim item = TryCast(XNode.ReadFrom(reader), XElement)
If name IsNot Nothing AndAlso item IsNot Nothing Then
Dim tempRoot = <Root>
<Name><%= name.Value %></Name>
<%= item %>
</Root>
Yield item
End If
ElseIf Not reader.Read() Then
Exit While
End If
End While
End If
Loop While reader.Read()
End Using
End Function
Sub Main()
Dim xmlTree = <Root>
<%=
From el In 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
Este código gera o seguinte resultado:
<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>