CodeGym /Cursos /C# SELF /LINQ to XML: XDocument, XElement

LINQ to XML: XDocument, XElement

C# SELF
Nivel 48 , Lección 3
Disponible

1. Introducción

El trabajo con XML en .NET dio un salto revolucionario con la aparición de LINQ. Antes, para modificar XML, había que escribir construcciones pesadas con NodeList y XmlElement, pero ahora podemos expresar todas las operaciones en estilo LINQ. Es como pasar de caminos de grava a una autopista rápida y moderna.

LINQ to XML resuelve tres tareas principales:

  • Facilita la sintaxis para construir y leer XML.
  • Hace que la navegación por el documento sea parecida a trabajar con colecciones.
  • Permite integrar XML con cualquier consulta LINQ.

Clases principales: XDocument, XElement, XAttribute

Antes de lanzarnos a las profundidades de LINQ to XML, conozcamos a los tres pilares:

  • XDocument — es la raíz de todo el documento, como la portada de un libro, que contiene todo.
  • XElement — cualquier nodo (elemento) XML individual. Es como una caja en un almacén, que puede contener otras cajas o productos (elementos, atributos y texto).
  • XAttribute — atributo de un elemento, por ejemplo, <Usuario id="42">.

Diagrama visual

+-----------------+
|   XDocument     |---------------------+
+-----------------+                     |
            |                      (XElement raíz)
            v                           |
      +-------------+              +-----------------+
      |   XElement  |<-------------| XElement (raíz) |
      +-------------+              +-----------------+
            |                              |
        (XAttribute)                (XElements anidados)

2. Leer XML con LINQ to XML

Supongamos que en nuestra app educativa (por ejemplo, una libreta de direcciones) queremos leer contactos desde un archivo XML. La estructura del archivo sería así:

<Contacts>
  <Person id="1">
    <Name>Juan Marquez</Name>
    <Email>ivan@example.com</Email>
    <Phones>
      <Phone type="mobile">+7 999 123-45-67</Phone>
      <Phone type="home">+7 812 123-45-67</Phone>
    </Phones>
  <Person>
  <Person id="2">
    <Name>Maria Peterson</Name>
    <Email>maria@example.com</Email>
    <Phones>
      <Phone type="mobile">+7 921 123-45-67</Phone>
    </Phones>
  </Person>
</Contacts>

Leer XML en memoria

Primero, agregamos en el proyecto la directiva:

using System.Xml.Linq;

Luego, leemos el archivo o cadena:

// Supón que xmlString contiene el XML, o también puedes usar XDocument.Load("contacts.xml")
var doc = XDocument.Parse(xmlString);

// Obtenemos la lista de Person
var persons = doc.Root.Elements("Person");

foreach (var person in persons)
{
    string name = person.Element("Name")?.Value ?? "Sin nombre";
    string email = person.Element("Email")?.Value ?? "Sin e-mail";
    string id = person.Attribute("id")?.Value ?? "sin id";
    
    Console.WriteLine($"Nombre: {name}, Email: {email}, id: {id}");
}

Explicación:

  • doc.Root devuelve el elemento raíz (Contacts).
  • Elements("Person") trae todos los <Person>.
  • Luego, accedemos a los elementos/atributos con .Element("...") o .Attribute("...").
  • El operador ?. evita errores si no encuentra el elemento o atributo.

Analogía

Trabajar con XElement es como abrir una matrioska: sacas una, dentro hay otras, y así sucesivamente. Solo que ahora, con una consulta sencilla, en lugar de recorrer todo a mano.

3. Crear consultas LINQ sobre XML

¿Quieres obtener todos los teléfonos móviles de todos los contactos? ¡Fácil! Con LINQ:

var mobilePhones = doc
    .Descendants("Phone") // busca todos los <Phone> en cualquier nivel
    .Where(p => (string)p.Attribute("type") == "mobile")
    .Select(p => p.Value);

foreach (string phone in mobilePhones)
{
    Console.WriteLine($"Móvil: {phone}");
}
  • Descendants("Phone") busca todos los <Phone> en el documento.
  • Attribute("type") obtiene el atributo.
  • La conversión (string)attribute devuelve la cadena o null automáticamente.

Trucos útiles

Puedes buscar en varios niveles sin preocuparte por la profundidad. Esto no se puede hacer fácilmente con XmlDocument sin complicaciones.

4. Crear y modificar XML "sobre la marcha"

Es muy cómodo crear XML en memoria usando los constructores de XElement y XDocument. Por ejemplo, agregar un nuevo contacto a nuestro documento (como parte de la app de libreta):

// Crear un nuevo Person
var newPerson = new XElement("Person",
    new XAttribute("id", "3"),
    new XElement("Name", "Sergey Novikov"),
    new XElement("Email", "sergey@example.com"),
    new XElement("Phones",
        new XElement("Phone", new XAttribute("type", "work"), "+7 812 987-65-43")
    )
);

// Añadir al raíz
doc.Root.Add(newPerson);

// Guardar en archivo o cadena
doc.Save("contacts.xml");

Comentarios:

  • Se pueden anidar elementos con constructores "matrioska": el primer argumento es la etiqueta, los siguientes, contenido anidado (puede ser elementos, atributos, texto).
  • Los atributos siempre van antes del contenido directo (ejemplo: <Phone type="work">).

5. Trabajar con colecciones C# — magia LINQ

Frecuentemente, necesitas serializar una colección de objetos (como List<Person>) a XML o viceversa. Es muy sencillo, casi con una línea LINQ:

var people = new List<Person>
{
    new Person { Id = 1, Name = "Juan Marquez", Email = "ivan@example.com" },
    // más objetos...
};

var doc = new XDocument(
    new XElement("Contacts",
        people.Select(p =>
            new XElement("Person",
                new XAttribute("id", p.Id),
                new XElement("Name", p.Name),
                new XElement("Email", p.Email)
            )
        )
    )
);

Clase Person para completar:

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}

Así tienes una conversión simple, clara y controlada de colección C# a XML.

6. Métodos útiles y trucos con XElement y XDocument

Navegación

  • .Elements("TagName") — todos los elementos hijos directos.
  • .Descendants("TagName") — todos los elementos anidados en cualquier nivel.
  • .Parent — elemento padre.
  • .Ancestors() — todos los antecesores (útil para buscar rutas).
  • .FirstNode, .LastNode, .NextNode, .PreviousNode — navegar entre nodos vecinos.

Buscar con condiciones

var personsWithEmail = doc.Root
    .Elements("Person")
    .Where(p => !string.IsNullOrEmpty((string)p.Element("Email")));

Agregar y eliminar

// Añadir teléfono a un usuario existente
var maria = doc.Root.Elements("Person")
    .FirstOrDefault(p => (string)p.Element("Name") == "Maria Peterson");

maria?.Element("Phones")?.Add(
    new XElement("Phone", new XAttribute("type", "work"), "+7 812 111-22-33")
);

// Eliminar usuario por id
doc.Root.Elements("Person")
    .Where(p => (string)p.Attribute("id") == "3")
    .Remove();

Transformar de vuelta a cadena/archivo

string xmlText = doc.ToString(); // con indentación
doc.Save("contacts.xml");

7. Consejos útiles

Comparación LINQ to XML con otros enfoques (.NET)

Enfoque Cuándo usar Ventajas Desventajas
XmlDocument Obsoleto, código viejo Integración con XPath Mucho código, API compleja
XmlSerializer Para serializar objetos Fácil convertir C# ↔ XML Poca flexibilidad
LINQ to XML Todo lo demás: parsing, edición Lineal, conciso, potente, LINQ Un poco más de memoria

Usos en proyectos reales y entrevistas

LINQ to XML se usa mucho para:

  • Parsear archivos de configuración sin un mapeo claro a clases C#.
  • Importar/exportar datos (ejemplo, cargar o guardar datos de usuario).
  • Migraciones, procesar XML "sucio" o complejo.
  • Transformaciones rápidas (convertir un XML a otro formato).

"¿Para qué me sirve esto?" — en entrevistas, te pueden pedir transformar o parsear XML complicado, y con LINQ to XML lo harás más fácil y elegante que con APIs viejas.

8. Particularidades y errores comunes

Al trabajar con LINQ to XML, los novatos suelen topar con algunos detalles:

  • Si accedes a un elemento que puede faltar, usa ?. o comprueba por null, o te saltarás un NullReferenceException.
  • No olvides que atributos y elementos son cosas distintas. A veces, parte de la info está en atributos y otra en elementos. Ejemplo:
  • <User login="admin"><Status>active</Status></User>
    
    Para obtener login, usas .Attribute("login"), y para el estado, .Element("Status").
  • Si experimentas con .Remove(), recuerda que esto cambia la estructura en memoria inmediatamente y no devuelve nada.
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION