次の方法で共有


ヘッダー情報にアクセスして XML フラグメントをストリーム配信する方法 (LINQ to XML)

大きな XML ファイルを任意に読み取り、アプリケーションのメモリ使用量を予想できるようにアプリケーションを作成しなければならない場合があります。 大きな XML ファイルを XML ツリーに設定しようとすると、ファイルのサイズに比例してメモリが過剰に使用されます。 したがって、代わりにストリーミングの手法を使用する必要があります。

これを実現する 1 つの選択肢として、XmlReader を使用してアプリケーションを作成する方法があります。 ただし、場合によっては、XML ツリーに対してクエリを実行するとき、LINQ の使用が必要になることがあります。 その場合は、カスタムの軸メソッドを独自に記述できます。 詳細については、LINQ to XML 軸メソッドを記述する方法に関するページを参照してください。

独自の軸メソッドを記述するには、XmlReader を使用して、対象となるノードの 1 つに到達するまでノードを読み取る小さなメソッドを記述します。 このメソッドから ReadFrom が呼び出され、これにより XmlReader からデータが読み取られ、XML フラグメントがインスタンス化されます。 さらに、カスタムの軸メソッドを列挙するメソッドに対しては、yield return を使用して各フラグメントが生成されます。 これで、カスタムの軸メソッド上に LINQ クエリを記述できます。

ストリーミングの手法は、ソース ドキュメントを 1 回だけ処理する必要がある場合に適しており、ドキュメントの順序で要素を処理できます。 OrderBy などの一部の標準クエリ演算子では、ソースが反復処理され、すべてのデータが収集され並べ替えられて、最終的にはシーケンス内の最初の項目が生成されます。 最初の項目を生成する前にソースを具体化するクエリ演算子を使用すると、メモリ占有領域を低く維持することができません。

例: URI で指定されたファイルから XML フラグメントをストリーム配信するカスタムの軸メソッドを実装して使用する

ストリーム出力は関心の高い問題となる場合があるため、例を使って説明します。 次の XML ドキュメントでは、カスタムの軸メソッドのコンシューマーが、各項目が属している顧客の名前も認識している必要があります。

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

この例で採用している方法では、ヘッダー情報の監視と保存が行われ、その後でヘッダー情報と列挙される詳細情報の両方が含まれている小さな XML ツリーが構築されます。 次に、軸メソッドによってこの新しい小さな XML ツリーが生成されます。 これでクエリは、詳細情報だけでなくヘッダー情報にもアクセスできるようになります。

この方法で使用されるメモリは少量です。 詳細な XML フラグメントが個々に生成されるときに、前のフラグメントへの参照は保持されず、そのフラグメントはガベージ コレクションの対象になります。 この手法を使用すると、存続期間の短いオブジェクトがヒープ上に多数作成されます。

次の例では、URI で指定されたファイルから XML フラグメントをストリーム出力する、カスタムの軸メソッドを実装して使用する方法を示します。 このカスタムの軸は、CustomerNameItem の各要素を含んだドキュメントを前提として記述されています。また、それらの要素は、上記の Source.xml ドキュメントと同じように配置されます。 これは単純な実装です。 ただし、より堅牢に実装する場合は、無効なドキュメントの解析にも対応するようにします。

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

このコードを実行すると、次の出力が生成されます。

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