Przesyłanie strumieniowe fragmentów kodu XML z dostępem do informacji nagłówka (LINQ to XML)

Czasami trzeba odczytywać dowolnie duże pliki XML i zapisywać aplikację w taki sposób, aby zużycie pamięci przez aplikację było przewidywalne. Jeśli spróbujemy wypełnić drzewo XML dużym plikiem XML, użycie pamięci będzie proporcjonalne do rozmiaru pliku — czyli nadmiernego. W związku z tym należy zamiast tego użyć techniki przesyłania strumieniowego.

Jedną z opcji jest napisanie aplikacji przy użyciu .XmlReader Można jednak użyć LINQ do wykonywania zapytań w drzewie XML. Jeśli tak, możesz napisać własną metodę osi niestandardowej. Aby uzyskać więcej informacji, zobacz Jak napisać metodę LINQ to XML osi.

Aby napisać własną metodę osi, napisz małą metodę, XmlReader która używa metody do odczytywania węzłów, dopóki nie osiągnie jednego z węzłów, które Cię interesują. Następnie metoda wywołuje metodę ReadFrom, która odczytuje z XmlReader obiektu i iniekuje fragment XML. Następnie daje każdy fragment do yield return metody, która wylicza metodę osi niestandardowej. Następnie możesz napisać zapytania LINQ w niestandardowej metodzie osi.

Techniki przesyłania strumieniowego najlepiej stosować w sytuacjach, w których konieczne jest przetwarzanie dokumentu źródłowego tylko raz i można przetwarzać elementy w kolejności dokumentu. Niektóre standardowe operatory zapytań, OrderBytakie jak , iterują źródło, zbierają wszystkie dane, sortują je, a następnie dają pierwszy element w sekwencji. Jeśli używasz operatora zapytania, który materializuje jego źródło przed uzyskaniem pierwszego elementu, nie zachowasz małego śladu pamięci.

Przykład: implementowanie i używanie niestandardowej metody osi, która przesyła strumieniowo fragmenty KODU XML z pliku określonego przez wartość URI

Czasami problem staje się nieco bardziej interesujący. W poniższym dokumencie XML odbiorca niestandardowej metody osi musi również znać nazwę klienta, do którego należy każdy element.

<?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>

Podejście, które przyjmuje ten przykład, to również obserwowanie tych informacji nagłówka, zapisywanie informacji nagłówka, a następnie tworzenie małego drzewa XML zawierającego zarówno informacje nagłówka, jak i wyliczane szczegóły. Metoda osi następnie daje to nowe, małe drzewo XML. Następnie zapytanie ma dostęp do informacji nagłówka, a także szczegółowych informacji.

Takie podejście ma niewielkie zużycie pamięci. W przypadku każdego szczegółowego fragmentu kodu XML nie są przechowywane żadne odwołania do poprzedniego fragmentu i jest on dostępny do wyrzucania elementów bezużytecznych. Ta technika tworzy wiele krótkotrwałych obiektów na stercie.

W poniższym przykładzie pokazano, jak zaimplementować i użyć niestandardowej metody osi, która przesyła strumieniowo fragmenty KODU XML z pliku określonego przez wartość URI. Ta oś niestandardowa jest napisana Customerw taki sposób, że oczekuje dokumentu, który zawiera elementy , NameItem i oraz że te elementy zostaną ułożone tak, jak w powyższym dokumencieSource.xml. Jest to uproszczona implementacja. Bardziej niezawodna implementacja byłaby przygotowana do analizy nieprawidłowego dokumentu.

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

Ten kod spowoduje wygenerowanie następujących danych wyjściowych:

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