Jak przesyłać strumieniowo fragmenty XML z dostępem do informacji nagłówka (LINQ to XML)

Czasami trzeba odczytać dowolnie duże pliki XML i zapisać aplikację, aby pamięć aplikacji jest przewidywalna. Jeśli spróbujesz 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 użyć techniki przesyłania strumieniowego.

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

Aby napisać własną metodę osi, należy napisać małą metodę, która używa XmlReader elementu do odczytu węzłów, dopóki nie osiągnie jednego z węzłów, w których cię interesuje. Następnie metoda wywołuje ReadFrommetodę , która odczytuje element z obiektu XmlReader i tworzy wystąpienie fragmentu XML. Następnie zwraca każdy fragment yield return do metody wyliczającej metodę osi niestandardowej. Następnie możesz napisać zapytania LINQ w metodzie osi niestandardowej.

Techniki przesyłania strumieniowego najlepiej stosować w sytuacjach, w których trzeba przetworzyć dokument źródłowy tylko raz i można przetwarzać elementy w kolejności dokumentu. Niektóre standardowe operatory zapytań, takie jak OrderBy, iterują swoje źródło, zbierają wszystkie dane, sortują je, a następnie w końcu 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: Zaimplementuj i użyj niestandardowej metody osi, która przesyła strumieniowo fragmenty XML z pliku określonego przez identyfikator URI

Czasami problem staje się nieco bardziej interesujący. W poniższym dokumencie XML użytkownik metody osi niestandardowej 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ściem, które przyjmuje ten przykład, jest również obserwowanie informacji o nagłówku, zapisanie informacji nagłówka, a następnie skompilowanie małego drzewa XML zawierającego zarówno informacje nagłówka, jak i szczegóły, które są wyliczane. Następnie metoda osi 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 miarę zwracania poszczególnych szczegółów fragmentu XML żadne odwołania nie są przechowywane do poprzedniego fragmentu i są dostępne do odzyskiwania pamięci. 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 XML z pliku określonego przez identyfikator URI. Ta oś niestandardowa jest zapisywana w taki sposób, że oczekuje dokumentu zawierającego Customerelementy , Namei Item oraz że te elementy zostaną rozmieszczone tak jak w powyższym Source.xml dokumencie. Jest to uproszczona implementacja. Bardziej niezawodna implementacja byłaby przygotowana do analizowania 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>