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