Procedimiento para transmitir fragmentos XML con acceso a información de encabezado (LINQ to XML)
A veces debe leer arbitrariamente los archivos XML grandes y escribir la aplicación para que la superficie de memoria de esta sea predecible. Si intenta rellenar un árbol XML con un archivo XML de gran tamaño, su utilización de memoria será proporcional al tamaño del archivo (es decir, excesivo). Por consiguiente, debe utilizar en su lugar una técnica de transmisión por secuencias.
Una opción consiste en escribir la aplicación usando XmlReader. Pero quizás prefiera usar LINQ para consultar el árbol XML. Si es así, puede escribir su propio método de eje personalizado. Para obtener más información, vea Cómo escribir un método de eje LINQ to XML.
Para escribir su propio método de eje, escriba un pequeño método que use XmlReader para leer nodos hasta que llegue a uno de ellos en el que esté interesado. A continuación el método llama a ReadFrom, que lee de XmlReader y crea una instancia de un fragmento XML. Luego ofrece cada fragmento por medio de yield return
al método que está enumerando el método de eje personalizado. A continuación puede escribir las consultas LINQ en su método de eje personalizado.
Las técnicas de transmisión por secuencias se aplican mejor en situaciones en las que el documento de origen solo se debe procesar una vez y se pueden procesar los elementos en el orden del documento. Ciertos operadores de consulta estándar, como OrderBy, recorren en iteración su origen, recaban todos los datos, los ordenan y finalmente producen el primer elemento de la secuencia. Si usa un operador de consulta que materializa su origen antes de ofrecer el primer elemento, no retendrá una superficie de memoria pequeña.
Ejemplo: Implementación y uso de un método de eje personalizado que transmite fragmentos XML del archivo especificado por un URI
A veces el problema se pone un poco más interesante. En el siguiente documento XML, el consumidor de su método de eje personalizado también tiene que conocer el nombre del cliente al que pertenece cada elemento.
<?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>
El enfoque que toma este ejemplo consiste en observar también esta información de encabezado, guardar la información de encabezado y después compilar un pequeño árbol XML que contiene la información de encabezado y el detalle que está enumerando. El método de eje ofrece este nuevo y pequeño árbol XML. La consulta tiene acceso a la información de encabezado así como a la información detallada.
Este enfoque ocupa una pequeña superficie de memoria. Como se ofrecen todos los fragmentos XML detallados, no se guardan referencias al fragmento anterior, que está disponible para la recolección de elementos no utilizados. Esta técnica crea muchos objetos de vida corta en el montón.
En el siguiente ejemplo se muestra cómo implementar y utilizar un método de eje personalizado que transmite por secuencias fragmentos XML del archivo especificado por la URI. Este eje personalizado se ha escrito para que espere un documento que tiene los elementos Customer
, Name
y Item
, y que esos elementos se ordenarán como en el documento Source.xml
anterior. Se trata de una implementación simplista. Una implementación más sólida estaría preparada para analizar un documento no válido.
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
Este código genera el siguiente resultado:
<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>