Bagikan melalui


Cara melakukan streaming fragmen XML dengan akses ke informasi header (LINQ ke XML)

Terkadang Anda harus membaca file XML yang amat besar, dan menulis aplikasi Anda sehingga jejak memori aplikasi dapat diprediksi. Jika Anda mencoba mengisi pohon XML dengan file XML yang sangat besar, penggunaan memori Anda akan sebanding dengan ukuran file itu{^>__<^}yaitu, berlebihan. Oleh karena itu, Anda harus menggunakan teknik streaming sebagai gantinya.

Salah satu opsinya adalah menulis aplikasi Anda menggunakan XmlReader. Namun, Anda mungkin ingin menggunakan LINQ untuk mengkueri pohon XML. Jika demikian, Anda dapat menulis metode sumbu kustom Anda sendiri. Untuk informasi selengkapnya, lihat Cara menulis metode sumbu LINQ ke XML.

Untuk menulis metode sumbu Anda sendiri, Anda menulis metode kecil yang menggunakan XmlReader untuk membaca simpul sampai mencapai salah satu simpul yang Anda minati. Metode kemudian memanggil ReadFrom, yang membaca dari XmlReader dan membuat instans fragmen XML. Kemudian menghasilkan setiap fragmen yield return hingga metode yang merinci metode sumbu kustom Anda. Anda kemudian dapat menulis kueri LINQ pada metode sumbu kustom Anda.

Teknik streaming paling baik diterapkan dalam situasi di mana Anda perlu memproses dokumen sumber hanya sekali, dan Anda dapat memproses elemen dalam urutan dokumen. Operator kueri standar tertentu, seperti OrderBy, mengulang sumbernya, mengumpulkan semua data, mengurutkan, lalu akhirnya menghasilkan item pertama dalam urutan. Jika Anda menggunakan operator kueri yang mewujudkan sumbernya sebelum menghasilkan item pertama, Anda tidak akan mempertahankan jejak memori kecil untuk aplikasi Anda.

Contoh: Menerapkan dan menggunakan metode sumbu kustom yang mengalirkan fragmen XML dari file yang ditentukan oleh URI

Terkadang masalahnya menjadi sedikit lebih menarik. Dalam dokumen XML berikut, konsumen metode sumbu kustom Anda juga harus mengetahui nama pelanggan yang memiliki tiap item itu.

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

Pendekatan yang diambil contoh ini adalah juga mengawasi informasi header ini, menyimpan informasi header, lalu membangun pohon XML kecil yang berisi informasi header dan detail yang Anda hitung. Metode sumbu kemudian menghasilkan pohon XML baru dan kecil ini. Kueri kemudian memiliki akses ke informasi header serta informasi detail.

Pendekatan ini memiliki jejak memori yang kecil. Karena setiap fragmen XML detail dihasilkan, tidak ada referensi yang disimpan ke fragmen sebelumnya, dan tersedia untuk pengumpulan sampah. Teknik ini menciptakan banyak objek berumur pendek pada tumpukan.

Contoh berikut menunjukkan cara mengimplementasikan dan menggunakan metode sumbu kustom yang mengalirkan fragmen XML dari file yang ditentukan oleh URI. Sumbu kustom ini ditulis sedemikian rupa sehingga mengharapkan dokumen yang memiliki elemen Customer, Name, dan Item lalu elemen-elemen tersebut akan diatur seperti dalam dokumen Source.xml di atas. Ini adalah implementasi yang sederhana. Namun, implementasi yang lebih kuat perlu disiapkan untuk mengurai dokumen yang tidak valid.

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

Kode ini menghasilkan output berikut:

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