Partilhar via


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, Namee 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>