c# - 执行此 LINQ to XML 查询的更好方法?

标签 c# .net linq-to-xml

假设我有这个 XML 文件:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Root>
  <Category Name="Tasties">
    <Category Name="Pasta">
      <Category Name="Chicken">
        <Recipe Name="Chicken and Shrimp Scampi" />
        <Recipe Name="Chicken Fettucini Alfredo" />
      </Category>
      <Category Name="Beef">
        <Recipe Name="Spaghetti and Meatballs" />
        <Recipe Name="Lasagna" />
      </Category>
      <Category Name="Pork">
        <Recipe Name="Lasagna" />
      </Category>
      <Category Name="Seafood">
        <Recipe Name="Chicken and Shrimp Scampi" />
      </Category>
    </Category>
  </Category>
</Root>

我想返回 Tasties\Pasta\Chicken 中所有食谱的名称,我该怎么做?

我目前拥有的是:

var q = from chk in
            (from c in doc.Descendants("Category")
             where c.Attribute("Name").Value == "Chicken"
             select c)
        select from r in chk.Descendants("Recipe")
               select r.Attribute("Name").Value;

foreach (var recipes in q)
{
    foreach (var recipe in recipes)
    {
        Console.WriteLine("Recipe name = {0}", recipe);
    }
}

虽然它不检查路径,但它只对第一个名为 Chicken 的类别有效。我可以递归地挖掘路径中的每个元素,但似乎我可能缺少更好的解决方案。我当前的查询也返回 IEnumerable<IEnumerable<String>>当我想要的只是一个 IEnumerable<String> .

基本上我可以让它工作,但它看起来很乱,我希望看到任何 LINQ 建议或技术来更好地查询。

最佳答案

就个人而言,我会使用 XmlDocument 和熟悉的 SelectNodes:

foreach(XmlElement el in doc.DocumentElement.SelectNodes(
   "Category[@Name='Tasties']/Category[@Name='Pasta']/Category[@Name='Chicken']/Recipe")) {
    Console.WriteLine(el.GetAttribute("Name"));
}

对于 LINQ-to-XML,我猜(未经测试)类似于:

var q = from c1 in doc.Root.Elements("Category")
        where c1.Attribute("Name").Value == "Tasties"
        from c2 in c1.Elements("Category")
        where c2.Attribute("Name").Value == "Pasta"
        from c3 in c2.Elements("Category")
        where c3.Attribute("Name").Value == "Chicken"
        from recipe in c3.Elements("Recipe")
        select recipe.Attribute("Name").Value;
foreach (string name in q) {
    Console.WriteLine(name);
}

编辑:如果希望分类选择更灵活:

    string[] categories = { "Tasties", "Pasta", "Chicken" };
    XDocument doc = XDocument.Parse(xml);
    IEnumerable<XElement> query = doc.Elements();
    foreach (string category in categories) {
        string tmp = category;
        query = query.Elements("Category")
            .Where(c => c.Attribute("Name").Value == tmp);
    }
    foreach (string name in query.Descendants("Recipe")
        .Select(r => r.Attribute("Name").Value)) {
        Console.WriteLine(name);
    }

这现在应该适用于任意数量的级别,选择所选级别或以下级别的所有食谱。


编辑讨论(评论)为什么 Where 有一个本地 tmp 变量:

这可能有点复杂,但我正在努力公正地回答这个问题;-p

基本上,foreach(带有迭代器左值“捕获”)看起来像:

class SomeWrapper {
    public string category;
    public bool AnonMethod(XElement c) {
        return c.Attribute("Name").Value == category;
    }
}
...
SomeWrapper wrapper = new SomeWrapper(); // note only 1 of these
using(var iter = categories.GetEnumerator()) {
    while(iter.MoveNext()) {
        wrapper.category = iter.Current;
        query = query.Elements("Category")
             .Where(wrapper.AnonMethod);
    }
}

这可能不是很明显,但是由于 Where 没有被立即评估,category 的值(通过谓词 AnonMethod)是直到很久以后才检查。这是 C# 规范的精确细节的不幸结果。引入 tmp(在 foreach 内)意味着每次迭代都会发生捕获:

class SecondWrapper {
    public string tmp;
    public bool AnonMethod(XElement c) {
        return c.Attribute("Name").Value == tmp;
    }
}
...
string category;
using(var iter = categories.GetEnumerator()) {
    while(iter.MoveNext()) {
        category = iter.Current;
        SecondWrapper wrapper = new SecondWrapper(); // note 1 per iteration
        wrapper.tmp = category;
        query = query.Elements("Category")
             .Where(wrapper.AnonMethod);
    }
}

因此,我们现在评估还是稍后评估都没有关系。复杂而困惑。你可以明白为什么我赞成改变规范!!!

关于c# - 执行此 LINQ to XML 查询的更好方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/873037/

相关文章:

c# - 我真的需要使用 Intent 来调用 Activity 吗?

c# - 读取收到的 json 数据时出错。我正在使用 C# 开发一个 UWP,它从 json 读取数据

.net - 无法修复错误 : This configuration section cannot be used at this path

c# - 在 C# 中概括和聚合 XML 转储的最佳方法是什么?

c# - 如何在 XDocument.Load(string uri) 上设置超时?

c# - linq to xml 性能

c# - 获取列表列表中的最大值列表

c# - App挂起后恢复相机资源

c# - 为什么每次引用源集合时,Automapper 都会将一个具体集合映射到一个新实例?

sql - 一个数据库用于一个客户或一个用于所有客户