Delen via


XML-fragmenten streamen met toegang tot headerinformatie (LINQ naar XML)

Soms moet u willekeurige grote XML-bestanden lezen en uw toepassing schrijven, zodat de geheugenvoetafdruk van de toepassing voorspelbaar is. Als u probeert een XML-structuur te vullen met een groot XML-bestand, is het geheugengebruik evenredig met de grootte van het bestand, dat wil gezegd overmatig. Daarom moet u in plaats daarvan een streamingtechniek gebruiken.

Een optie is om uw toepassing te schrijven met behulp van XmlReader. Het is echter mogelijk dat u LINQ wilt gebruiken om een query uit te voeren op de XML-structuur. Zo ja, dan kunt u uw eigen aangepaste asmethode schrijven. Zie Een LINQ schrijven naar een XML-asmethode voor meer informatie.

Als u uw eigen asmethode wilt schrijven, schrijft u een kleine methode die gebruikmaakt van de XmlReader knooppunten om knooppunten te lezen totdat deze een van de knooppunten bereikt waarin u geïnteresseerd bent. Met de methode wordt vervolgens een XML-fragment aangeroepen ReadFromen geïnstitueerd XmlReader . Vervolgens wordt elk fragment geretourneerd yield return naar de methode die de aangepaste asmethode opsommen. Vervolgens kunt u LINQ-query's schrijven op uw aangepaste asmethode.

Streamingtechnieken worden het beste toegepast in situaties waarin u het brondocument slechts één keer moet verwerken en u de elementen in documentvolgorde kunt verwerken. Bepaalde standaardqueryoperators, zoals OrderByhet herhalen van hun bron, verzamelen alle gegevens, sorteren en vervolgens het eerste item in de reeks opleveren. Als u een queryoperator gebruikt die de bron materialiseert voordat u het eerste item oplevert, behoudt u geen kleine geheugenvoetafdruk.

Voorbeeld: Een aangepaste asmethode implementeren en gebruiken waarmee XML-fragmenten worden gestreamd uit het bestand dat is opgegeven door een URI

Soms wordt het probleem iets interessanter. In het volgende XML-document moet de consument van uw aangepaste asmethode ook de naam weten van de klant waartoe elk item behoort.

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

De benadering die in dit voorbeeld wordt gebruikt, is om ook te kijken naar deze headergegevens, de headergegevens op te slaan en vervolgens een kleine XML-structuur te maken die zowel de headergegevens als de details bevat die u opsommen. De asmethode levert vervolgens deze nieuwe, kleine XML-structuur op. De query heeft vervolgens toegang tot de headergegevens en de detailgegevens.

Deze benadering heeft een kleine geheugenvoetafdruk. Aangezien elk detail-XML-fragment wordt geretourneerd, worden er geen verwijzingen naar het vorige fragment bewaard en is het beschikbaar voor garbagecollection. Deze techniek creëert veel kortdurende objecten op de heap.

In het volgende voorbeeld ziet u hoe u een aangepaste asmethode implementeert en gebruikt waarmee XML-fragmenten worden gestreamd uit het bestand dat is opgegeven door de URI. Deze aangepaste as wordt zodanig geschreven dat een document met Customer, Nameen Item elementen wordt verwacht en dat deze elementen worden gerangschikt zoals in het bovenstaande Source.xml document. Het is een simplistische implementatie. Een krachtigere implementatie zou bereid zijn om een ongeldig document te parseren.

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

Deze code produceert de volgende uitvoer:

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