Vorgehensweise: Streamen von XML-Fragmenten mit Zugriff auf Headerinformationen
Es kann vorkommen, dass Sie willkürlich große XML-Dateien lesen und Ihre Anwendung so schreiben müssen, dass der Arbeitsspeicherbedarf der Anwendung vorhersehbar ist.Wenn Sie versuchen, eine XML-Struktur mit einer großen XML-Datei zu füllen, ändert sich Ihre Speicherbeanspruchung proportional zur Größe der Datei, also exzessiv.Deshalb sollten Sie stattdessen ein Streamingverfahren verwenden.
Zu diesem Zweck können Sie die Anwendung mit XmlReader schreiben.Es empfiehlt sich aber häufig, zum Abfragen der XML-Struktur LINQ zu verwenden.Bei Verwendung von können Sie eine eigene benutzerdefinierte Achsenmethode schreiben.Weitere Informationen dazu finden Sie unter Vorgehensweise: Schreiben einer LINQ to XML-Achsenmethode.
Zum Schreiben einer eigenen Achsenmethode müssen Sie zunächst eine kleine Methode schreiben, die mit dem XmlReader Knoten so lange liest, bis sie zu einem der Knoten gelangt, die Sie interessieren.Die Methode ruft dann die ReadFrom-Methode auf, die aus dem XmlReader liest und ein XML-Fragment instanziiert.Daraufhin wird jedes Fragment mit yield return an die Methode zurückgegeben, die Ihre benutzerdefinierte Achsenmethode aufzählt.Sie können dann LINQ-Abfragen für die benutzerdefinierte Achsenmethode schreiben.
Streamingverfahren eignen sich vor allem für Situationen, bei denen Sie das Quelldokument nur einmal verarbeiten müssen und bei denen die Elemente in der Reihenfolge verarbeitet werden können, in der sie im Dokument auftreten.Einige Standardabfrageoperatoren, wie OrderBy, durchlaufen ihre Quelle, erfassen alle Daten, sortieren sie und geben dann das erste Element in der Sequenz zurück.Beachten Sie, dass Sie bei der Verwendung eines Abfrageoperators, der seine Quelle vor der Rückgabe des ersten Elements materialisiert, keine minimale Speicherbeanspruchung aufrechterhalten können.
Beispiel
Manchmal wird das Problem noch ein wenig interessanter.Im folgenden XML-Dokument muss der Benutzer Ihrer benutzerdefinierten Achsenmethode auch die Namen der Kunden kennen, zu denen die einzelnen Elemente gehören.
<?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>
Dieses Beispiel sucht auch nach diesen Headerinformationen, speichert sie und konstruiert dann eine kleine XML-Struktur, die sowohl die Headerinformationen als auch die Detailinformationen enthält, die von Ihnen aufgezählt werden.Die Achsenmethode gibt daraufhin diese neue, kleine XML-Struktur zurück.Die Abfrage verfügt damit sowohl über Zugriff auf die Headerinformationen als auch über Zugriff auf die Detailinformationen.
Dieser Ansatz führt zu einer geringen Speicherbeanspruchung.Da jedes XML-Detailfragment zurückgegeben wird, bleiben keine Verweise auf vorherige Fragmente erhalten. Es steht damit für die Garbage Collection (automatische Speicherbereinigung) zur Verfügung.Beachten Sie, dass bei diesem Verfahren viele kurzlebige Objekte auf dem Heap erstellt werden.
Im folgenden Beispiel wird die Vorgehensweise bei der Implementierung und Verwendung einer benutzerdefinierten Achsenmethode gezeigt, die XML-Fragmente aus der durch den URI angegebenen Datei streamt.Diese benutzerdefinierte Achse ist so geschrieben, dass sie ein Dokument mit Customer-Elementen, Name-Elementen und Item-Elementen erwartet. Diese Elemente werden wie im Dokument Source.xml oben angeordnet.Dabei handelt es sich um eine eher einfache Implementierung.Eine robustere Implementierung würde darauf vorbereitet sein, ein ungültiges Dokument analysieren zu können.
Hinweis |
---|
Im folgenden Beispiel wird das C#-yield return-Konstrukt verwendet.Entsprechender Code wird in Visual Basic unter Verwendung einer Klasse, die die IEnumerable(Of XElement)-Schnittstelle implementiert, bereitgestellt.Ein Beispiel für die Implementierung von IEnumerable(Of T) in Visual Basic finden Sie unter Exemplarische Vorgehensweise: Implementieren von IEnumerable(Of T) in Visual Basic. |
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 are 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
Dieser Code erzeugt die folgende Ausgabe:
<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>