【问题标题】:Complicated XML structure being hard to query with LINQ to XML复杂的 XML 结构难以使用 LINQ to XML 查询
【发布时间】:2012-12-28 20:14:29
【问题描述】:

工作中的一位同事在尝试查询一个非常不寻常的 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 属性 是“教育” 并且只有当这个 具有教育属性的条形节点的 V 值不包含我们在 LINQ 中指定的单词

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

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

【问题讨论】:

  • 与论坛网站不同,我们不使用“谢谢”、“任何帮助表示赞赏”或Stack Overflow 上的签名。请参阅“Should 'Hi', 'thanks,' taglines, and salutations be removed from posts?.
  • 是否有理由使用 linq 来完成这项任务?这里的业务规则听起来更适合 system.xml
  • 首先,不会再发生了,约翰和霍根,为什么不使用 LINQ?它应该执行 System.xml 查询 XML 结构的所有操作,但速度更快,代码行更少,速度更快,或者我错了吗?这不是围绕 LINQ to XML 的全部想法吗?为什么在这里使用 XML 节点查询比使用 LINQ 更好?
  • @Erez - 正如我所说 - 根据用例,一个比另一个更好。如果您不知道如何在 linq 中执行此操作,但知道如何在 System.xml 中执行此操作,这很好地表明 a) System.xml 更好或 b) 您不了解 Linq。我的猜测是 a),但我正在为您提供 linq 答案,请给我几分钟
  • 开始和结束标签&lt;MAinFoo&gt;&lt;/MainFoo&gt;有错别字

标签: c# linq linq-to-xml


【解决方案1】:

tl;博士:

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 查询的完整源代码,用于测试和玩自己。

处理 foo 元素的主要位置。然后它会检查元素(特别是“Bar”元素及其属性)是否符合您希望应用的规则。

这里的关键问题是这种查询的可维护性如何。你能像这样维护一个 linq 查询吗?尝试使用 LinqPad——我相信它会让您(或任何人)更轻松地修改和开发这些查询。


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

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

到上述查询的末尾。

当然,这不包括 John 性感的错误检查。


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

【讨论】:

    【解决方案2】:

    为了让您的选择保持开放,这里有一个使用 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
    }
    

    【讨论】:

    • +1 巧妙的解决方案(但搜索字符串不区分大小写)
    【解决方案3】:
    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");
    

    【讨论】:

    • 使用正则匹配和使用String.Equals(attribute, text, StringComparison.CurrentCultureIgnoreCase)会有什么区别吗?
    • @IchabodClay 同意,这也是选项,如果不需要匹配单词
    • 当我发表那条评论时,我并没有好好思考。正则表达式在这种情况下更好:P
    【解决方案4】:

    以下似乎有效:

    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”元素具有“Education”的“N”属性和“Specific Text”的“V”属性的那些(您可以把你想要的任何谓词放在那里)。对于每个选定的元素,它会提取“用户”元素(假设有一个 ,然后解析并返回“ID”属性。

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

    【讨论】:

    • 谢谢约翰。我在我的问题中犯了一些错误,你的回答对我的问题很好,但问题是我需要特定 Foo 节点中 Bar 节点的 V 属性,它的值是 UserID,即是什么让它变得更复杂....如果您不介意,请再次查看我的编辑...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多