C# 实现LINQ到XML查询的更好方法?
假设我有这个XML文件:C# 实现LINQ到XML查询的更好方法?,c#,.net,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" />
<?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>
哪种方法有效,尽管它不检查路径,只检查第一个名为Chicken的类别。我可以递归地挖掘路径中的每个元素,但似乎我缺少了一个更好的解决方案。另外,当我只需要一个IEnumerable
时,我当前的查询返回IEnumerable
基本上我可以让它工作,但它看起来很混乱,我希望看到任何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到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);
}
这可能不明显,但由于中的不会立即求值,因此直到很久以后才会检查类别的值(通过谓词AnonMethod
)。这是C#spec精确细节的不幸结果。引入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);
}
}
因此,我们现在评估还是以后评估并不重要。复杂而混乱。你可以看到为什么我喜欢改变规格 我个人会使用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到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);
}
这可能不明显,但由于中的不会立即求值,因此直到很久以后才会检查类别的值(通过谓词AnonMethod
)。这是C#spec精确细节的不幸结果。引入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);
}
}
因此,我们现在评估还是以后评估并不重要。复杂而混乱。你可以看到为什么我喜欢改变规格 这里的代码与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
语句。由于嵌套的select语句,您得到了IEnumerable
。这里的代码与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
语句。由于嵌套的select语句,您得到的是IEnumerable
。如果您选择System.Xml.XPath,则将向XDocument添加XPathSelectElements()扩展方法。这将允许您使用XPath语句选择节点,如果您对此比较满意的话
否则,您可以使用SelectMany将IEnumerable
展平为一个IEnumerable
:
IEnumerable<IEnumerable<String>> foo = myLinqResults;
IEnumerable<string> bar = foo.SelectMany(x => x);
IEnumerable foo=myLinqResults;
IEnumerable bar=foo.SelectMany(x=>x);
如果您选择System.Xml.XPath,这将向XDocument添加XPathSelectElements()扩展方法。这将允许您使用XPath语句选择节点,如果您对此比较满意的话
否则,您可以使用SelectMany将IEnumerable
展平为一个IEnumerable
:
IEnumerable<IEnumerable<String>> foo = myLinqResults;
IEnumerable<string> bar = foo.SelectMany(x => x);
IEnumerable foo=myLinqResults;
IEnumerable bar=foo.SelectMany(x=>x);
有点晚了,但扩展方法确实有助于清理外观凌乱的LINQ-to-XML查询。对于您的场景,您可以使用如下代码:
var query = xml.Root
.Category("Tasties")
.Category("Pasta")
.Category("Chicken")
.Recipes();
。。。使用我在中介绍的一些技术有点晚了,但是扩展方法确实可以帮助清理外观混乱的LINQ-to-XML查询。对于您的场景,您可以使用如下代码:
var query = xml.Root
.Category("Tasties")
.Category("Pasta")
.Category("Chicken")
.Recipes();
。。。使用我在中介绍的一些技术,这是VB.NET的一大优势-您可以使用原生ASP样式的XML文本和Linq2XML查询,而无需通过方法和字符串访问属性等;-):p尽管我不能忍受VB语法的其余部分,如果我不得不读的话,我可以读它,但每次都会让我畏缩。你的句子“我当前的查询也返回IEnumerable>,而我只需要一个IEnumerable。”似乎遗漏了什么。为了保留尖括号,用`像这样的字符IEnumerable
。哦,我甚至没有注意到,我会解决这个问题注意:我为级别数量灵活的情况添加了一个不同的示例,这是VB.NET的最大优点之一-您可以使用原生ASP样式的XML文本和Linq2XML查询,而无需通过方法和字符串访问属性等;-):p尽管我不能忍受VB语法的其余部分,如果我不得不读的话,我可以读它,但每次都会让我畏缩。你的句子“我当前的查询也返回IEnumerable>,而我只需要一个IEnumerable。”似乎遗漏了什么。为了保留尖括号,用`characters thisIEnumerable
。哦,我甚至没有注意到,我将修复这个问题。注意,我添加了一个不同的示例,用于级别数灵活的情况。很酷,我不知道DocumentElement.SelectNodes()(我对xml编程相当陌生)(注意我将LINQ改为XML)@Davy8-第一个示例是XmlDocument,第二个示例显示XDocumentRight,第二个答案更长,但更容易以更强类型的方式重构为可重用的东西。是的;LINQ“Where”方法使用延迟执行。“foreach”construct捕获左值;这意味着如果“foreach”中没有“tmp”作用域,则为每个级别捕获相同的变量-这通常意味着最后一个值(“Chicken”)用于每个级别。尝试删除它;您将看到没有匹配项。整个“foreach”/“左值”/captured variable issue是一个已知的问题。我过去曾与Edic+Mads交谈过,看看是否可能是cha