CodeGym /Các khóa học /C# SELF /LINQ to XML: XDocument, XElement

LINQ to XML: XDocument, XElement

C# SELF
Mức độ, Bài học
Có sẵn

1. Giới thiệu

Sự xuất hiện của LINQ đã tạo ra một cuộc cách mạng trong cách làm việc với XML trên .NET. Trước đây để thay đổi XML hay phải viết những cấu trúc rườm rà với NodeList và XmlElement, giờ ta có thể biểu đạt mọi thao tác theo phong cách LINQ. Giống như bạn chuyển sang đường cao tốc sau khi mò mẫm trên đường sỏi đá lâu ngày.

LINQ to XML giải quyết ba vấn đề chính:

  • Đơn giản hóa cú pháp tạo và đọc XML.
  • Biến việc điều hướng tài liệu giống như làm việc với collection.
  • Cho phép tích hợp XML vào bất kỳ truy vấn LINQ nào.

Các lớp chính: XDocument, XElement, XAttribute

Trước khi lao xuống phần sâu của LINQ to XML, hãy làm quen với ba trụ cột:

  • XDocument — là gốc của toàn bộ tài liệu, giống như bìa tập giấy, nơi chứa mọi thứ.
  • XElement — bất kỳ nút (element) XML riêng lẻ nào. Giống như một hộp trong kho, bên trong có thể có hộp khác hoặc hàng hóa (elements, attributes và text).
  • XAttribute — attribute của element, ví dụ <User id="42" />.

Sơ đồ trực quan


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

2. Đọc XML bằng LINQ to XML

Giả sử trong ứng dụng học của chúng ta (ví dụ: sổ địa chỉ) ta muốn đọc các contact từ file XML. Cấu trúc file có thể như sau:

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

Đọc XML vào bộ nhớ

Trước tiên thêm chỉ thị sau vào dự án:

using System.Xml.Linq;

Bây giờ đọc file hoặc chuỗi:

// Giả sử xmlString chứa XML document, hoặc có thể dùng XDocument.Load("contacts.xml")
var doc = XDocument.Parse(xmlString);

// Lấy danh sách Person
var persons = doc.Root.Elements("Person");

foreach (var person in persons)
{
    string name = person.Element("Name")?.Value ?? "Không có tên";
    string email = person.Element("Email")?.Value ?? "Không có email";
    string id = person.Attribute("id")?.Value ?? "không có id";
    
    Console.WriteLine($"Tên: {name}, Email: {email}, id: {id}");
}

- doc.Root trả về element gốc (Contacts).
- Elements("Person") trả về tất cả <Person>.
- Sau đó lấy các element/attribute cần thiết bằng .Element("...") hoặc .Attribute("...").
- Toán tử ?. bảo vệ khỏi lỗi nếu element/attribute không tồn tại.

So sánh

Làm việc với XElement giống như tháo bộ búp bê matryoshka: lấy một cái, bên trong có cái khác, rồi lại có thứ khác nữa. Chỉ khác là giờ bạn làm được điều đó bằng một truy vấn gọn nhẹ, không phải những vòng lặp rườm rà.

3. Viết truy vấn LINQ trên XML

Muốn lấy danh sách tất cả số điện thoại di động của mọi người? Dễ thôi, làm theo phong cách LINQ!

var mobilePhones = doc
    .Descendants("Phone") // tìm mọi Phone ở bất kỳ cấp nào
    .Where(p => (string)p.Attribute("type") == "mobile")
    .Select(p => p.Value);

foreach (string phone in mobilePhones)
{
    Console.WriteLine($"Di động: {phone}");
}

- Descendants("Phone") tìm mọi tag <Phone> trong document.
- Attribute("type") — lấy attribute.
- Ép kiểu (string) cho attribute tự động trả về chuỗi hoặc null.

Mẹo hay

Bạn có thể tìm qua nhiều mức lồng nhau mà không cần lo lắng về cấu trúc phức tạp. Điều này khó mà làm gọn nếu chỉ dùng XmlDocument truyền thống.

4. Tạo và chỉnh sửa XML "on the fly"

XML rất tiện để tạo trực tiếp trong bộ nhớ bằng các constructor của XElement và XDocument. Ví dụ thêm một người mới vào document (nhớ, đây là phần của app sổ địa chỉ đang phát triển):

// Tạo Person mới
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")
    )
);

// Thêm vào gốc
doc.Root.Add(newPerson);

// Lưu lại vào file (hoặc chuỗi)
doc.Save("contacts.xml");

Ghi chú:

- Có thể lồng các element bằng constructor "matryoshka": tham số đầu là tag, các tham số sau là nội dung lồng (có thể là element, attribute, text).
- Attribute luôn nằm trước nội dung trực tiếp (ví dụ <Phone type="work">...).

5. Tương tác với collection C# — ma thuật của LINQ

Thường ta cần serialize một collection object bình thường (ví dụ List<Person>) thành XML hoặc ngược lại. Làm rất đơn giản, gần như chỉ một dòng LINQ:

var people = new List<Person>
{
    new Person { Id = 1, Name = "Ivan Ivanov", Email = "ivan@example.com" },
    // và tiếp tục
};

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)
            )
        )
    )
);
Lớp Person trông như sau (để đầy đủ):
public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}

Giờ bạn có một chuyển đổi đơn giản, trực quan và dễ kiểm soát từ collection C# sang XML!

6. Các phương thức và thủ thuật hữu ích của XElement và XDocument

Điều hướng

  • .Elements("TagName") — tất cả các element trực tiếp (con trực tiếp).
  • .Descendants("TagName") — tất cả các element lồng bên trong ở bất kỳ đâu.
  • .Parent — element cha.
  • .Ancestors() — tất cả các ancestor của element (hữu ích để tìm đường dẫn).
  • .FirstNode, .LastNode, .NextNode, .PreviousNode — điều hướng giữa các node lân cận.

Tìm kiếm với điều kiện

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

Thêm và xóa

// Thêm số điện thoại cho người dùng đã tồn tại
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")
);

// Xóa người dùng theo id
doc.Root.Elements("Person")
    .Where(p => (string)p.Attribute("id") == "3")
    .Remove();

Chuyển ngược lại thành chuỗi/file

string xmlText = doc.ToString(); // có thụt lề
doc.Save("contacts.xml");

7. Các mẹo hữu ích

So sánh LINQ to XML với các cách tiếp cận khác (.NET)

Cách tiếp cận Khi nào dùng Ưu điểm Nhược điểm
XmlDocument
Mã cũ, legacy Tích hợp với XPath Nhiều code, API phức tạp
XmlSerializer
Dùng để serialize objects Dễ serialize C# ↔ XML Ít linh hoạt
LINQ to XML
Mọi thứ còn lại: parsing, sửa đổi Gọn, ngắn gọn, mạnh mẽ, LINQ Tốn bộ nhớ hơi nhiều hơn

Ứng dụng trong dự án thực tế và phỏng vấn

  • Phân tích file cấu hình khi không có mapping rõ ràng tới class C#.
  • Import/export dữ liệu (ví dụ xuất/nhập dữ liệu người dùng).
  • Migration, xử lý XML "bẩn" hoặc cấu trúc phức tạp.
  • Chuyển đổi nhanh (ví dụ chuyển từ một format XML sang format khác).

"Tại sao mình phải biết mấy cái này?" — trong phỏng vấn có thể họ yêu cầu chuyển đổi hoặc parse một XML phức tạp, và nếu biết LINQ to XML bạn sẽ làm việc đó nhanh và đẹp hơn nhiều so với dùng API cũ.

8. Những lỗi thường gặp khi làm việc với LINQ to XML

Lỗi #1: cố truy cập element không tồn tại mà không kiểm tra.
Nếu element có thể không tồn tại, dùng toán tử ?. hoặc kiểm tra trước null. Nếu không bạn sẽ gặp NullReferenceException và crash runtime.

Lỗi #2: nhầm lẫn giữa attribute và element.
Attribute và element là hai thứ khác nhau trong XML. Đôi khi dữ liệu được đặt ở attribute, đôi khi ở element. Ví dụ:

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

Để lấy giá trị attribute login dùng .Attribute("login"), còn để lấy nội dung thẻ <Status> dùng .Element("Status").

Lỗi #3: hiểu nhầm hành vi của phương thức .Remove().
Phương thức .Remove() ngay lập tức thay đổi cấu trúc XML trong bộ nhớ. Nó không trả về gì, và không thể "undo" sau khi gọi. Người mới đôi khi nghĩ nó sẽ trả về phần tử đã xóa hoặc báo hiệu gì đó — không phải vậy.

2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Trích xuất dữ liệu từ tài liệu XML
Trích xuất dữ liệu từ tài liệu XML
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION