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 Customer
elementy , Name
i 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);
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
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>