复杂的XML结构很难用LINQ to XML进行查询



一位同事在试图查询一个非常不寻常的XML文件时遇到了问题,在试图帮助他之后,我和其他人遇到了一些创造性的障碍。。。。看看这个,这里的许多人可能会感兴趣。。。。

结构:

<Root>
 <MainFoo>
     <Foo>
        <A bla="bla" />
        <B bla1="blablabla" />
        <C bla2="blabla" />
        <Bar N="Education" V="Some Text" />
        <Bar N="Other Node" V="Some other Text" />
        <Bar N="Yet Other Node" V="Some other other Text" />
        <Bar N="fourth Bar Node" V="Some other other otherText" />
        <Bar N="UserID" V="1" />
     </Foo>
     <Foo>
        <A bla="bla" />
        <B bla1="blablabla" />
        <C bla2="blabla" />
        <Bar N="Education" V="Specific Text" />
        <Bar N="Other Node" V="Some other Text" />
        <Bar N="Yet Other Node" V="Some other other Text" />
        <Bar N="fourth Bar Node" V="Some other other otherText" />
        <Bar N="UserID" V="2" />
     </Foo>
     <Foo>
        <A bla="bla" />
        <B bla1="blablabla" />
        <C bla2="blabla" /> <!--***No Bar node with N="Education" in this Foo Node, not a mistake! this might be part of the problem but this is the XML Structure and can't be changed***-->
        <Bar N="Other Node" V="Some other Text" />
        <Bar N="Yet Other Node" V="Some other other Text" />
        <Bar N="fourth Bar Node" V="Some other other otherText" />
        <Bar N="UserID" V="3" />
     </Foo>
     <Foo>
        <A bla="bla" />
        <B bla1="blablabla" />
        <C bla2="blabla" />
        <Bar N="Education" V="Specific Text" />
        <Bar N="Other Node" V="Some other Text" />
        <Bar N="Yet Other Node" V="Some other other Text" />
        <Bar N="fourth Bar Node" V="Some other other otherText" />
        <Bar N="UserID" V="4" />
     </Foo>
 </MainFoo>
 <OtherMainFoo></OtherMainFoo>
 <MoreMainFoo></MoreMainFoo>
</Root>

好的,现在是手头的问题:我们正在尝试使用LINQ to XML将每个用户节点的每个用户ID值转换为每个Foo元素的字符串IF在这个Foo中有一个Bar节点,并且这个Bar节点的N属性为"Education"不包含我们在LINQ中指定的单词

例如,如果我们希望具有education的Foo节点的所有用户ID都不包含单词"Some">,我们将得到2,4的结果,因为Foo编号1有一个具有N属性的education值的Bar节点,但它在V属性中有Some字符串,Foo编号3没有在其N属性中具有education值(非常重要,因为我们认为这是我们无论做什么都会得到空结果的原因之一(。

这里的任何LINQ到XML专家都有一个想法,这对XML来说是一个非常不寻常的场景,但这是我们必须处理的,我相信这个问题会引起这里很多人的兴趣。

tl;dr:

var hasEducation = contacts.Elements("MainFoo").Elements("Foo")
 .Where(foo => foo.Elements("Bar")
                 .Any(bar => (bar.Attribute("N").Value == "Education") &&
                     (!bar.Attribute("V").Value.ToLower().Contains("some") )))

注意:我用LinqPad测试了这个(http://www.linqpad.net/)使用它并喜欢它。LinqPad非常适合这些问题。以下是LinqPad查询的完整来源,供您自己测试和发挥。

main where正在处理foo的一个元素。然后,它检查元素(特别是"条形"元素及其属性(,以查找要应用的规则。

这里的关键问题是这种类型的查询的可维护性如何。你能像这样维护linq查询吗?试着使用LinqPad——我相信它会让你(或任何人(更容易修改和开发这些查询


要获得用户ID列表(正如John的回答(,您可以添加

.Element("User").Attribute("ID").Value; 

到上面查询的末尾。

当然,这还不包括约翰性感的错误检查。


XElement contacts = XElement.Parse (@"
<Root>
 <MainFoo>
     <Foo>
        <A bla='bla' />
        <B bla1='blablabla' />
        <C bla2='blabla' />
        <Bar N='Education' V='Some Text' />
        <Bar N='Other Node' V='Some other Text' />
        <Bar N='Yet Other Node' V='Some other other Text' />
        <Bar N='fourth Bar Node' V='Some other other otherText' />
        <User ID='1' />
     </Foo>
     <Foo>
        <A bla='bla' />
        <B bla1='blablabla' />
        <C bla2='blabla' />
        <Bar N='Education' V='Specific Text' />
        <Bar N='Other Node' V='Some other Text' />
        <Bar N='Yet Other Node' V='Some other other Text' />
        <Bar N='fourth Bar Node' V='Some other other otherText' />
        <User ID='2' />
     </Foo>
     <Foo>
        <A bla='bla' />
        <B bla1='blablabla' />
        <C bla2='blabla' /> <!--***No Bar node with N='Education' in this Foo Node, not a mistake! this might be part of the problem but this is the XML Structure and can't be changed***-->
        <Bar N='Other Node' V='Some other Text' />
        <Bar N='Yet Other Node' V='Some other other Text' />
        <Bar N='fourth Bar Node' V='Some other other otherText' />
        <User ID='3' />
     </Foo>
     <Foo>
        <A bla='bla' />
        <B bla1='blablabla' />
        <C bla2='blabla' />
        <Bar N='Education' V='Specific Text' />
        <Bar N='Other Node' V='Some other Text' />
        <Bar N='Yet Other Node' V='Some other other Text' />
        <Bar N='fourth Bar Node' V='Some other other otherText' />
        <User ID='4' />
     </Foo>
 </MainFoo>
 <OtherMainFoo></OtherMainFoo>
 <MoreMainFoo></MoreMainFoo>
</Root>");
var hasEducation = contacts.Elements("MainFoo").Elements("Foo")
      .Where(foo => foo.Elements("Bar")
               .Any(bar => (bar.Attribute("N").Value == "Education") &&
                           (!bar.Attribute("V").Value.ToLower().Contains("some") )))
      .Dump();

为了保持选项的开放性,这里有一个使用XPath而不是LINQ的解决方案。这不包括根据约翰的回答进行错误检查,但它仍然有效。

public static IEnumerable<string> GetIDs(XDocument doc, string negation)
{
    //The following xpath string will select all Foo elements that contain a Bar child
    // that has a N attribute with the value "Education" and also has a V attribute
    // that does not contain the specified string.
    string xPathString = String.Format("//Foo[(Bar/@N = 'Education') and (not(contains(Bar/@V, '{0}')))]", negation);
    return doc.Root
              .XPathSelectElements(xPathString) //Select the proper Foo elements
              .Select(a => a.Element("User").Attribute("ID").Value); //Grab the User elements under the previous Foo elements and return their ID attribute value
}
string text = "Some";
var query = from foo in xdoc.Descendants("Foo")
            let user = foo.Element("User")
            where user != null &&
                  foo.Elements("Bar")
                     .Any(bar => (string)bar.Attribute("N") == "Education" &&
                                 !Regex.IsMatch((string)bar.Attribute("V"), text,
                                                RegexOptions.IgnoreCase))
            select (int)user.Attribute("ID");
// result: 2, 4

我使用正则表达式在bar的属性中搜索单词有两个原因——使搜索不区分大小写,以及处理Bar元素不具有V属性的情况。您还可以更改模式以匹配单词(而不是单词的一部分(。


如果所有的Foo节点都有User元素,则可以删除对用户的空检查。同样,如果Bar元素总是包含V属性,并且不需要不区分大小写的搜索,那么查询可以简化:

var query = from foo in xdoc.Descendants("Foo")                     
            where foo.Elements("Bar")
                        .Any(bar => (string)bar.Attribute("N") == "Education" &&
                                    !((string)bar.Attribute("V")).Contains(text))
            select (int)foo.Element("User").Attribute("ID");

以下似乎有效:

public static IEnumerable<int> QueryComplexXml()
{
    var doc = XDocument.Parse(XML);
    if (doc.Root == null)
    {
        throw new System.InvalidOperationException("No root");
    }
    var mainFoo = doc.Root.Element("MainFoo");
    if (mainFoo == null)
    {
        throw new System.InvalidOperationException("No MainFoo");
    }
    var userIDs = from foo in mainFoo.Elements("Foo")
                  where
                      foo.Elements("Bar")
                         .Any(
                             bar =>
                             bar.Attribute("N").Value == "Education" &&
                             bar.Attribute("V").Value == "Specific Text")
                  let user = foo.Element("User")
                  where user != null
                  select int.Parse(user.Attribute("ID").Value);
    return userIDs;
}

该代码考虑了所有的"Foo"元素,但只考虑那些有"Bar"元素的元素,该元素的"N"属性为"Education","V"属性为为"Specific Text"(你可以在那里放任何你想要的谓词(。对于每一个选择的元素,它都会提取"User"元素(假设一个(,并解析并返回"ID"属性

在您发布的示例XML中,返回2和4。

相关内容

  • 没有找到相关文章

最新更新