所以说我有这个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建议或技术来做更好的查询。
答案 0 :(得分:3)
就个人而言,我会使用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);
}
}
因此,我们现在或以后评估无关紧要。复杂而凌乱。你可以看到为什么我赞成改变规范!!!
答案 1 :(得分:1)
这里的代码类似于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);
}
一般来说,我会说你只想在LINQ查询中使用一个select
语句。由于您的嵌套选择语句,您获得了IEnumerable<IEnumerable<String>>
。
答案 2 :(得分:1)
如果您为System.Xml.XPath add a using statement,则会向您的XDocument添加XPathSelectElements()扩展方法。如果您对此更加满意,那么这将允许您选择带有XPath语句的节点。
否则,您可以使用SelectMany将IEnumerable <
IEnumerable <
字符串>>
展平为IEnumerable <
字符串>
:
IEnumerable<IEnumerable<String>> foo = myLinqResults;
IEnumerable<string> bar = foo.SelectMany(x => x);
答案 3 :(得分:1)
有点晚了,但扩展方法确实可以帮助清理凌乱的LINQ to XML查询。对于您的场景,您可以使用以下代码:
var query = xml.Root
.Category("Tasties")
.Category("Pasta")
.Category("Chicken")
.Recipes();
中展示的一些技巧