首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> ASP.NET 基礎 >> ASP.NET 基礎

LINQ to XML 實戰筆記:XML 設定檔的 CRUD 完整範例

     編輯:ASP.NET 基礎

前言

System.Xml.Linq (LINQ to XML) 是 .NET 3.5 起引入的現代 XML 操作 API,提供比 `XmlDocument` 更直觀、型別安全、可組合的查詢與修改體驗。本文以真實專案中的「子系統設定管理」為例,示範 XML 設定檔的完整增刪改查 (CRUD) 操作。

範例 XML 結構 (Config.xml)

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <OPsystemConfig>
    <MemberCenter>
      <DomainName>example.com</DomainName>
      <ProtocolName>HTTPS</ProtocolName>
      <APIKey>xxx</APIKey>
      <AESKey>yyy</AESKey>
      <AESVI>zzz</AESVI>
    </MemberCenter>
    <ChildSystems>
      <ChildSystem>
        <Name>Content</Name>
        <ControllerName>ContentManager</ControllerName>
      </ChildSystem>
      <ChildSystem>
        <Name>Image</Name>
        <ControllerName>ImageManager</ControllerName>
      </ChildSystem>
      <ChildSystem>
        <Name>Comment</Name>
        <ControllerName>CommentManager</ControllerName>
      </ChildSystem>
    </ChildSystems>
  </OPsystemConfig>
</configuration>

資料模型

public class SystemModuleConfig
{
    public string Name { get; set; }
    public string ControllerName { get; set; }
}

核心服務類別實作

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;

public class SystemModuleConfigService : ISystemModuleConfigService
{
    private readonly string _configPath;

    public SystemModuleConfigService(string configPath)
    {
        _configPath = configPath ?? throw new ArgumentNullException(nameof(configPath));
        EnsureFileExists();
    }

    private void EnsureFileExists()
    {
        if (!File.Exists(_configPath))
        {
            // 建立預設結構
            var doc = new XDocument(
                new XDeclaration("1.0", "utf-8", "yes"),
                new XElement("configuration",
                    new XElement("OPsystemConfig",
                        new XElement("MemberCenter"),
                        new XElement("ChildSystems")
                    )
                )
            );
            doc.Save(_configPath);
        }
    }

    private XDocument LoadDoc() => XDocument.Load(_configPath);

    private void SaveDoc(XDocument doc)
    {
        // 移除唯讀屬性再儲存
        var fi = new FileInfo(_configPath);
        if (fi.IsReadOnly) fi.IsReadOnly = false;
        doc.Save(_configPath);
    }

    // ---------- 查詢 ----------
    public IList GetList()
    {
        var doc = LoadDoc();
        return doc.Descendants("ChildSystem")
            .Select(e => new SystemModuleConfig
            {
                Name = (string)e.Element("Name"),
                ControllerName = (string)e.Element("ControllerName")
            })
            .ToList();
    }

    public SystemModuleConfig GetModel(string name)
    {
        var doc = LoadDoc();
        var elem = doc.Descendants("ChildSystem")
            .FirstOrDefault(e => (string)e.Element("Name") == name);
        return elem == null ? null : new SystemModuleConfig
        {
            Name = (string)elem.Element("Name"),
            ControllerName = (string)elem.Element("ControllerName")
        };
    }

    private bool Exists(string name)
    {
        var doc = LoadDoc();
        return doc.Descendants("ChildSystem")
            .Any(e => (string)e.Element("Name") == name);
    }

    // ---------- 新增 ----------
    public bool Add(string name, string controllerName)
    {
        if (Exists(name)) return false; // 已存在

        var doc = LoadDoc();
        var childSystems = doc.Descendants("ChildSystems").First();
        childSystems.Add(
            new XElement("ChildSystem",
                new XElement("Name", name),
                new XElement("ControllerName", controllerName)
            )
        );
        SaveDoc(doc);
        return true;
    }

    // ---------- 修改 ----------
    public bool Modify(string name, string controllerName)
    {
        var doc = LoadDoc();
        var elem = doc.Descendants("ChildSystem")
            .FirstOrDefault(e => (string)e.Element("Name") == name);
        if (elem == null) return false;

        elem.Element("ControllerName").Value = controllerName;
        SaveDoc(doc);
        return true;
    }

    // ---------- 刪除 ----------
    public bool Remove(string name)
    {
        var doc = LoadDoc();
        var elements = doc.Descendants("ChildSystem")
            .Where(e => (string)e.Element("Name") == name)
            .ToList(); // ToList 避免修改集合時迭代器失效

        if (!elements.Any()) return false;

        foreach (var el in elements) el.Remove();
        SaveDoc(doc);
        return true;
    }
}

關鍵技巧解析

1. 型別安全的值轉換

// 推薦:使用显式轉換 (string)element,自動處理 null
string name = (string)element.Element("Name"); // 若元素不存在回傳 null,不拋例外

// 不推薦:element.Element("Name").Value // 若元素不存在拋 NullReferenceException

2. 建構 XML 的函數式風格

// 巢狀建構,可讀性高
var newChild = new XElement("ChildSystem",
    new XElement("Name", name),
    new XElement("ControllerName", controllerName)
);
parentElement.Add(newChild);

3. 查詢與修改的分離

// 先查詢收集到 List,再逐一修改/刪除,避免「修改集合時無法迭代」錯誤
var items = doc.Descendants("ChildSystem").Where(...).ToList();
foreach (var item in items) item.Remove();

4. 儲存前處理唯讀屬性

var fi = new FileInfo(path);
if (fi.IsReadOnly) fi.IsReadOnly = false;
doc.Save(path);

5. 命名空間處理 (若 XML 有 xmlns)

XNamespace ns = "https://example.com/schema";
var query = doc.Descendants(ns + "ChildSystem");

進階:效能優化建議

  • 快取 XDocument:若讀取頻率高、檔案小,可在記憶體快取 `XDocument` 實例,定期或變更時才存盤。
  • 非同步 I/O:.NET 4.5+ 可用 `XDocument.LoadAsync` / `SaveAsync` 避免阻塞執行緒。
  • 大檔案改用 XmlReader/XmlWriter:超過 10MB 的 XML 建議改用流式處理,避免完整載入 DOM 佔用記憶體。

單元測試範例 (xUnit)

public class SystemModuleConfigServiceTests
{
    private readonly string _testFile = Path.Combine(Path.GetTempPath(), "test_config.xml");
    private readonly SystemModuleConfigService _service;

    public SystemModuleConfigServiceTests()
    {
        if (File.Exists(_testFile)) File.Delete(_testFile);
        _service = new SystemModuleConfigService(_testFile);
    }

    [Fact]
    public void Add_Then_GetList_ShouldContainNewItem()
    {
        _service.Add("TestModule", "TestController");
        var list = _service.GetList();
        Assert.Contains(list, x => x.Name == "TestModule" && x.ControllerName == "TestController");
    }

    [Fact]
    public void Modify_ExistingItem_ShouldUpdate()
    {
        _service.Add("ModTest", "OldCtrl");
        _service.Modify("ModTest", "NewCtrl");
        var model = _service.GetModel("ModTest");
        Assert.Equal("NewCtrl", model.ControllerName);
    }

    [Fact]
    public void Remove_ExistingItem_ShouldDelete()
    {
        _service.Add("DelTest", "Ctrl");
        _service.Remove("DelTest");
        Assert.Null(_service.GetModel("DelTest"));
    }
}

總結

LINQ to XML 以其函數式建構、強型別查詢、直觀的修改 API,大幅降低 XML 操作的複雜度。掌握 `XElement`/`XAttribute` 的建構、查詢、轉換與儲存模式,即可輕鬆應對設定檔管理、資料交換、報表產出等多種場景。記住:查詢用 LINQ,修改先 ToList,儲存前檔案屬性,是避坑三寶。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved