Udostępnij za pośrednictwem


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

    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>