CodeGym /Courses /C# SELF /LINQ to XML: XDocument

LINQ to XML: XDocument, XElement

C# SELF
Level 48 , Lesson 3
Available

1. Introduction

The arrival of LINQ sparked a revolution in working with XML in .NET. If before you had to write bulky constructs with NodeList and XmlElement to change XML, now we can express all operations in LINQ style. It's like switching to a fast highway after wandering long on gravel roads.

LINQ to XML solves three main tasks:

  • Makes the syntax for building and reading XML simpler.
  • Makes navigation through the document feel like working with collections.
  • Allows integrating XML with any LINQ query.

Main classes: XDocument, XElement, XAttribute

Before diving into the deep waters of LINQ to XML, let's get familiar with the three pillars:

  • XDocument — the root of the whole document, like the spine of a paper file that contains everything.
  • XElement — any single XML node (element). It's like a single box in a warehouse that may contain other boxes or goods (elements, attributes, and text).
  • XAttribute — an element attribute, for example, <User id="42" />.

Visual diagram


+-----------------+
|   XDocument     |---------------------+
+-----------------+                     |
            |                      (Root XElement)
            v                           |
      +-------------+              +-----------------+
      |   XElement  |<-------------| XElement (root) |
      +-------------+              +-----------------+
            |                              |
        (XAttribute)                (nested XElements)

2. Reading XML with LINQ to XML

Imagine that in our educational app (for example, an address book) we want to read contacts from an XML file. Suppose the file structure is like this:

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

Load XML into memory

First add this directive to the project:

using System.Xml.Linq;

Now read the file or string:

// Let xmlString contain the XML document, or you can use XDocument.Load("contacts.xml")
var doc = XDocument.Parse(xmlString);

// Get the list of Person
var persons = doc.Root.Elements("Person");

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

- doc.Root gives the root element (Contacts).
- Elements("Person") returns all <Person> elements.
- Then we get needed elements/attributes via .Element("...") or .Attribute("...").
- The ?. operator protects against errors if an element/attribute is not found.

Analogy

Working with XElement is like taking apart a matryoshka: you pull one out, inside there are others, and so on. Now it's done with a single lightweight query instead of clunky traversals.

3. Writing LINQ queries over XML

Want to get a list of all mobile phone numbers of all people? Sure — you can do it in LINQ style!

var mobilePhones = doc
    .Descendants("Phone") // find all Phone at any level
    .Where(p => (string)p.Attribute("type") == "mobile")
    .Select(p => p.Value);

foreach (string phone in mobilePhones)
{
    Console.WriteLine($"Mobile: {phone}");
}

- Descendants("Phone") finds all <Phone> tags in the document.
- Attribute("type") — get the attribute.
- Casting (string) to an attribute automatically yields a string or null.

Cool tricks

You can search across multiple levels without worrying about complex nesting. That's something not easily done with plain XmlDocument without a lot of hassle.

4. Creating and modifying XML on the fly

XML is convenient to create in memory using the XElement and XDocument constructors. Here's an example of adding a new person to our document (remember, this is part of our "growing address-book app"):

// Create new 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"), "+12 812 987-65-43")
    )
);

// Add to root
doc.Root.Add(newPerson);

// Save back to file (or string)
doc.Save("contacts.xml");

Comments:

- You can nest elements using "matryoshka" constructors: the first argument is the tag, the rest is nested content (can be elements, attributes, text).
- Attributes always go before the direct content (i.e. <Phone type="work">...).

5. Interacting with C# collections — LINQ magic

Often you need to serialize a regular collection of objects (for example, List<Person>) to XML or vice versa. It's done very simply, almost in one LINQ line:

var people = new List<Person>
{
    new Person { Id = 1, Name = "Ivan Ivanov", Email = "ivan@example.com" },
    // etc.
};

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)
            )
        )
    )
);
The Person class looks like this (for completeness):
public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}

Now you have a simple, clear and controllable transformation of a C# collection into XML!

6. Useful methods and techniques for XElement and XDocument

Navigation

  • .Elements("TagName") — all immediate child elements (direct descendants).
  • .Descendants("TagName") — all nested elements anywhere inside the current element.
  • .Parent — the parent element.
  • .Ancestors() — all ancestors of the element (useful to find the path).
  • .FirstNode, .LastNode, .NextNode, .PreviousNode — neighbor navigation.

Conditional searching

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

Adding and removing

// Add a phone to an existing user
var maria = doc.Root.Elements("Person")
    .FirstOrDefault(p => (string)p.Element("Name") == "Maria Petrova");

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

// Remove a user by id
doc.Root.Elements("Person")
    .Where(p => (string)p.Attribute("id") == "3")
    .Remove();

Convert back to string/file

string xmlText = doc.ToString(); // with indentation
doc.Save("contacts.xml");

7. Useful nuances

Comparing LINQ to XML with other .NET approaches

Approach When to use Advantages Disadvantages
XmlDocument
Legacy, old code Integration with XPath Lots of code, complex API
XmlSerializer
For object serialization Easy C# ↔ XML serialization Low flexibility
LINQ to XML
Everything else: parsing, editing Linear, concise, powerful, LINQ A bit more memory

Usage in real projects and interviews

  • Parsing config files where there's no strict mapping to C# classes.
  • Import/export of data (for example, exporting/importing user data).
  • Migrations, handling "dirty" or complex XML structures.
  • Quick transformations (for example, converting one XML format to another).

"Why should I know this?" — in an interview you might be asked to transform or parse some complex XML, and remembering LINQ to XML you'll do it more simply and neatly than with older APIs.

8. Common mistakes when working with LINQ to XML

Mistake #1: trying to access a missing element without checks.
If an element might be missing, use the ?. operator or a preliminary null check. Otherwise you'll get a NullReferenceException and an unexpected crash at runtime.

Mistake #2: confusing attributes and elements.
Attributes and elements are different entities in XML. Sometimes data is split between them. For example:

<User login="admin"><Status>active</Status></User>

To get the value of the login attribute use .Attribute("login"), and to get the content of the <Status> tag use .Element("Status").

Mistake #3: misunderstanding the behavior of .Remove().
The .Remove() method immediately changes the XML document structure in memory. It returns nothing, and you can't "undo" the removal. Beginners sometimes expect it to return removed elements or somehow signal the change — it doesn't.

2
Task
C# SELF, level 48, lesson 3
Locked
Extracting Data from an XML Document
Extracting Data from an XML Document
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION