代码之家  ›  专栏  ›  技术社区  ›  Davy8

更好的方法来执行这个linq-to-xml查询?

  •  1
  • Davy8  · 技术社区  · 15 年前

    假设我有这个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>
    

    我想把所有的味道、意大利面、鸡肉食谱的名字都还给你,我该怎么做?

    我现在拥有的是:

    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建议或技术来做更好的查询。

    4 回复  |  直到 15 年前
        1
  •  3
  •   Marc Gravell    15 年前

    我个人会用 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 (使用迭代器lvalue“captured”)如下所示:

    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);
        }
    }
    

    这可能并不明显,但因为 在哪里? 未立即评估,值为 category (通过谓词 AnonMethod )很晚才检查。这是C规范详细介绍的不幸结果。 川芎嗪 ( 范围内 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);
        }
    }
    

    因此,我们现在还是以后评估并不重要。复杂而混乱。您可以看到我为什么喜欢更改规范!!!

        2
  •  1
  •   CoderDennis    15 年前

    这里的代码类似于Marc的第二个示例,但它经过了测试和验证。

    var q = from t in doc.Root.Elements("Category")
            where t.Attribute("Name").Value == "Tasties"
            from p in t.Elements("Category")
            where p.Attribute("Name").Value == "Pasta"
            from c in p.Elements("Category")
            where c.Attribute("Name").Value == "Chicken"
            from r in c.Elements("Recipe")
            select r.Attribute("Name").Value;
    
    foreach (string recipe in q)
    {
        Console.WriteLine("Recipe name = {0}", recipe);
    }
    

    一般来说,我想说你只想要一个单人间 select LINQ查询中的语句。你得到了 IEnumerable<IEnumerable<String>> 因为您的嵌套select语句。

        3
  •  1
  •   Joel Mueller    15 年前

    如果你 add a using statement 对于system.xml.xpath,这将向xdocument添加xpathselectelements()扩展方法。这将允许您选择具有xpath语句的节点,如果您更愿意这样做的话。

    否则,您可以展平IEnumerable < 可枚举的 < >> 变成一个IEnumerable < 一串 > 使用selectmany:

    IEnumerable<IEnumerable<String>> foo = myLinqResults;
    IEnumerable<string> bar = foo.SelectMany(x => x);
    
        4
  •  1
  •   OdeToCode    15 年前

    稍微晚了一点,但是扩展方法确实可以帮助清理看起来杂乱无章的linq-to-xml查询。对于您的场景,您可以使用如下代码:

    var query = xml.Root
                   .Category("Tasties")
                   .Category("Pasta")
                   .Category("Chicken")
                   .Recipes();
    

    …使用我展示的一些技巧 From LINQ To XPath And Back Again