使用 LINQ 指定祖先节点从节点获取所有属性值

Getting all attribute values from node by specifying the ancestor node using LINQ

假设我有以下 xml 文件:

<ModuleSets>
    <ServiceModuleSet Name="serverModules">
        <Modules>
            <ServiceModule Name="ServerModule1"/>
            <ServiceModule Name="ServerModule2"/>
        </Modules>
    </ServiceModuleSet>
    <ServiceModuleSet Name="testBenchModules">
        <Modules>
            <ServiceModule Name="testBenchModule1"/>
            <ServiceModule Name="testBenchModule2"/>
        </Modules>
    </ServiceModuleSet>
    <ServiceModuleSet Name="hostComputerModules">
        <Modules>
        </Modules>
    </ServiceModuleSet>
</ModuleSets>

我想从 ServiceModule 节点获取所有属性,方法是在 ServiceModuleSet 节点中搜索,其中属性名称等于 "serverModules".

我是 LINQ 的新手,尝试了以下代码:

IEnumerable<string> allServerModules = from item in xmlDocument.Descendants("ServiceModuleSet")
    where item.Descendants("ServiceModuleSet").Any(attribute => attribute.Value == "serverModules")
    select (string)item.Attribute("Name");
    
foreach (var serverModule in allServerModules)
{
    Console.WriteLine(serverModule);
}

很遗憾,我没有将任何结果打印到控制台。使用 LINQ 解决它的正确方法是什么?

我喜欢用字典:

            XDocument doc = XDocument.Load(FILENAME);

            Dictionary<string, List<string>> dict = doc.Descendants("ServiceModuleSet")
                .GroupBy(x => (string)x.Attribute("Name"), y => y)
                .ToDictionary(x => x.Key, y => y.Descendants("ServiceModule").Select(z => (string)z.Attribute("Name")).ToList());

这是我的解决方案:

var parsedXml = XDocument.Load("sample.xml");

var serverModules = parsedXml.Descendants("ServiceModuleSet")
    .FirstOrDefault(set => set.Attribute("Name")?.Value == "serverModules");

var moduleNames = serverModules?.Descendants("ServiceModule")
    .Select(module => module.Attribute("Name")?.Value)
    .Where(value => !string.IsNullOrEmpty(value));

if (moduleNames == null) return;

foreach (var moduleName in moduleNames)
{
    Console.WriteLine(moduleName);
}
  1. 首先,我尝试检索 ServiceModuleSet,它的 Name 属性等于 "serverModules"
    1.1 如果没有任何后代(满足过滤条件)那么我们不想失败并出现异常,这就是为什么我使用 FirstOrDefault 而不是 First
    1.2 相同的容错逻辑也应用于 Name 属性,这就是我使用空条件运算符 (Attribute("Name")?.)
  2. 的原因
  3. 如果我找到了想要的节点,那么我可以寻找它的子节点来询问它们的 Name 属性
    2.1 再次使用空条件运算符 (serverModules?.Descendants) 使我们的代码更健壮
    2.2 ServiceModule 节点可能有也可能没有 Name 属性,这就是为什么我使用空条件运算符 (.Attribute("Name")?.) 和空字符串过滤 (!string.IsNullOrEmpty) 的组合。
  4. 如果我成功并检索到了一些数据,那么我会打印它们,否则我会提前退出