【问题标题】:Parse XML with Linq使用 Linq 解析 XML
【发布时间】:2011-06-13 02:41:46
【问题描述】:

我有以下 XML 文档,我想将其解析为 DataSet。

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> 
<Response Status="OK">
  <Item>
    <Field Name="ID">767147519</Field> 
    <Field Name="Name">Music</Field> 
    <Field Name="Path">Family\Music</Field> 
    <Field Name="Type">Playlist</Field> 
  </Item>
</Response>

我想获取 ID、Name 和 Path 的属性值。

以下是我尝试过的:

Dim loaded As XDocument = XDocument.Load(uriString)
Dim name = From c In loaded.Descendants("Item") Select c
For Each result In name
  Dim str1 = result.Attribute("ID").Value 'Returns Nothing and causes a validation error
  Dim str2 = result.Value ' Returns all the attribute values in one long string (ie "767147519MusicFamilyPlaylist")
Next

任何帮助将不胜感激。

谢谢,

马特

编辑:

按照下面的答案之一,我一直在尝试在我的 Linq 中实现匿名类型,但是我一直遇到错误

对象引用未设置为 对象的实例。

我的更新代码如下:

Dim name = From c In loaded.Descendants("Item") Select c Select sID = c.Element("Field").Attribute("Name").Value, sName = c.Attribute("ID").Value.FirstOrDefault
Dim Id As String = String.Empty
For Each result In name
  Id = result.sID
Next

我认为这个错误意味着无法定位属性(“ID”),所以我尝试了几种类似结果的变体。

有没有人能够确定我哪里出错并指出我正确的方向。

谢谢,

马特

【问题讨论】:

  • 我已经更新了我的问题以显示我尝试实现匿名类型,我遇到了一个对象引用错误(请参阅更新的问题),任何帮助确定此错误的原因将不胜感激。
  • 另外,添加了一个新标签,该应用程序在 VB.Net 中,因此 VB 中的示例将不胜感激,但任何帮助都很棒。
  • 你说你认为这个错误意味着无法定位属性“ID”,但问题是没有属性“ID”。有一个名为“Field”的元素和一个名为“Name”的属性。 Name 属性的为“ID”。您的代码试图做错事。您需要获取 .Attribute("Name").Value == "ID" 所在的 Field 元素的 .Value。不是“ID”属性的值,因为没有这样的东西。

标签: wpf vb.net .net-3.5 linq-to-xml


【解决方案1】:

您的代码中有一些错误:

您应该获得 XName 等于 Field 而不是 Item 的 Descendents

Dim name = From c In loaded.Descendants("Field") Select c

您所追求的属性称为Name,而不是ID

Dim str1 = result.Attribute("Name").Value

在您的每个 str1 的第一次迭代将是“ID”,下一个它将是“名称”,等等。

总代码:

Dim loaded As XDocument = XDocument.Load(uriString)
Dim name = From c In loaded.Descendants("Field") Select c
For Each result In name
  Dim str1 = result.Attribute("Name").Value 'Returns "ID"
  Dim str2 = result.Value ' Returns "767147519"
Next

【讨论】:

    【解决方案2】:

    我希望你期待这样的简短答案,而不是另一个实现:

    Dim items = From c In loaded.Descendants("Item") Select c (...)
    

    好的,到目前为止,应该没有任何问题。变量名 'name' 有点混乱,所以我把它改成了 'items'。

    第二部分包含错误:

    Dim items = (...) Select sID = c.Element("Field").Attribute("Name").Value, sName = c.Attribute("ID").Value.FirstOrDefault
    

    以下方法有效,因为有一个名为 Name 的属性,尽管结果是“ID”,这完全是意料之外的:

    c.Element("Field").Attribute("Name").Value
    

    错误来了:

    c.Attribute("ID").Value.FirstOrDefault
    

    c是XmlNode ' ...',没有任何属性,所以c.Attribute("ID")的结果为null。

    我猜你想要类似下面的东西:

    Dim loaded = XDocument.Load("XMLFile1.xml")
    Dim items = From item In loaded.Descendants("Item") Select _
                sID = (From field In item.Descendants("Field") _
                       Where field.Attribute("Name") = "ID" _
                       Select field.Value).FirstOrDefault() _
                , _
                sName = (From field In item.Descendants("Field") _
                         Where field.Attribute("Name") = "Name" _
                         Select field.Value).FirstOrDefault()
    

    【讨论】:

      【解决方案3】:

      经过进一步研究并在所提供答案的部分帮助下,我想出了以下内容,它返回了我所追求的信息。

      Dim Query = From items In loaded.Descendants("Item") _   
      Let sID = ( From q In items.Descendants("Field") _       
      Where q.Attribute("Name").Value = "ID" ) _ 
      Let sName = ( From r In items.Descendants("Field") _       
      Where r.Attribute("Name").Value = "Name" ) _ 
      Let sPath = ( From s In items.Descendants("Field") _       
      Where s.Attribute("Name").Value = "Path" ) _ 
      Where (Ctype(sPath.Value,String) Like "Family\*") _
      Select pId=sID.Value, pName=sName.Value, pPath = sPath.Value
      

      如果这可以通过任何方式改进以实现更好的性能,请告诉我。

      感谢大家的帮助,虽然没有一个答案能够完全解决问题,但通过大家的帮助,我能够学到很多关于Linq的知识。

      马特

      【讨论】:

        【解决方案4】:

        这是一个通用解决方案,可以处理多个项目中具有不同字段名称的所有字段。它将结果保存在一个表中,其中包含所有不同的字段名称作为列名称。

        Module Module1
        
        Function createRow(ByVal table As DataTable, ByVal item As XElement) As DataRow
            Dim row As DataRow = table.NewRow
        
            Dim fields = item.Descendants("Field")
            For Each field In fields
                row.SetField(field.Attribute("Name").Value, field.Value)
            Next
        
            Return row
        
        End Function
        
        
        Sub Main()
            Dim doc = XDocument.Load("XMLFile1.xml")
        
            Dim items = doc.Descendants("Item")
        
            Dim columnNames = From attr In items.Descendants("Field").Attributes("Name") Select attr.Value
        
            Dim columns = From name In columnNames.Distinct() Select New DataColumn(name)
        
            Dim dataSet As DataSet = New DataSet()
            Dim table As DataTable = New DataTable()
            dataSet.Tables.Add(table)
        
            table.Columns.AddRange(columns.ToArray())
        
            Dim rows = From item In items Select createRow(table, item)
        
            For Each row In rows
                table.Rows.Add(row)
            Next
        
            ' TODO Handle Table
        End Sub
        
        End Module
        

        我尝试尽可能多地使用 Linq,但是 Linq 在递归处理嵌套元素时有点不灵活。

        这是我使用的示例 xml 文件:

        <?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
        <Response Status="OK">
          <Item>
            <Field Name="ID">767147519</Field>
            <Field Name="Name">Music</Field>
            <Field Name="Path">Family\Music</Field>
            <Field Name="Type">Playlist</Field>
          </Item>
          <Item>
            <Field Name="ID">123</Field>
            <Field Name="Name">ABC</Field>
            <Field Name="RandomFieldName">Other Value</Field>
            <Field Name="Type">FooBar</Field>
          </Item>
        </Response>
        

        结果:

        ID         Name     Path          Type        RandomFieldName
        
        767147519  Music    Family\Music  Playlist
        
        123        ABC                    FooBar      Other Value
        

        【讨论】:

          【解决方案5】:

          使用 XPath 让每个人都免于烦恼?

          XmlDocument xml = new XmlDocument();
          xml.Load(xmlSource);
          
          string id = xml.SelectSingleNode("/Response/Item/Field[@Name='ID']").InnerText;
          string name = xml.SelectSingleNode("/Response/Item/Field[@Name='Name']").InnerText;
          string path = xml.SelectSingleNode("/Response/Item/Field[@Name='Path']").InnerText;
          

          【讨论】:

            【解决方案6】:

            您可以使用 XPath:

            Dim data = From item In loaded.Descendants("Item")
                       Select
                         ID = item.XPathSelectElement("Field[@Name='ID']").Value,
                         Name = item.XPathSelectElement("Field[@Name='Name']").Value,
                         Path = item.XPathSelectElement("Field[@Name='Path']").Value,
                         Type = item.XPathSelectElement("Field[@Name='Type']").Value
            

            (一定要导入System.Xml.XPath命名空间)

            或者直接添加到DataTable:

            Dim dt As New DataTable()
            dt.Columns.Add("ID")
            dt.Columns.Add("Name")
            dt.Columns.Add("Path")
            dt.Columns.Add("Type")
            For Each item In loaded.Descendants("Item")
              dt.Rows.Add(
                item.XPathSelectElement("Field[@Name='ID']").Value,
                item.XPathSelectElement("Field[@Name='Name']").Value,
                item.XPathSelectElement("Field[@Name='Path']").Value,
                item.XPathSelectElement("Field[@Name='Type']").Value
              )
            Next
            

            【讨论】:

              【解决方案7】:

              这是我为解决您的问题所做的尝试。我刚刚注意到您希望使用尽可能多的 LINQ,因此我已经相应地构建了我的 LINQ 查询。请注意,结果类型(对于“ID”)将为 IEnumerable(),即您需要在其上运行 for each 循环以获取单个 id,即使是单个项目:

              Dim loaded As XDocument = XDocument.Load(uriString)
              
              Dim IDs = From items In loaded.Descendants("Item") _
                       Let fields = items.Descendants("Field") _
                       From field In fields _
                       Where field.Attribute("Name").Value = "ID" _
                       Select field.Value
              

              附带说明:为了将来参考,如果您在示例中遇到 C# 匿名类型“var”,vb 中的等价物就像我上面的查询一样是普通的暗淡(没有“as type”部分)。

              希望这会有所帮助。 特立独行

              【讨论】:

                【解决方案8】:

                一个简单的解决方案是

                        var result = doc.Root.Descendants(XName.Get("Item")).Select(x =>  x.Descendants(XName.Get("Field")));
                
                
                        foreach (var v in result)
                        {
                            string id = v.Single(x => x.Attribute(XName.Get("Name")).Value == "ID").Value;
                
                            string name = v.Single(x => x.Attribute(XName.Get("Name")).Value == "Name").Value;
                
                            string path = v.Single(x => x.Attribute(XName.Get("Name")).Value == "Path").Value;
                
                            string type = v.Single(x => x.Attribute(XName.Get("Name")).Value == "Type").Value;
                
                        }
                

                它可以很容易地转换成vb代码。

                【讨论】:

                • "string id = v.Single(x => x.Attribute(XName.Get("Name")).Value == "ID").Value;"转换为 VB.NET?我还没有遇到过如何转换这种类型的 C# 编码。
                【解决方案9】:

                还有另一种方法可以解决此问题。将此 XML 转换为 DataSet 所需的格式,然后使用 DataSet.ReadXml 加载它。如果您不了解 XSLT,这将是一件很痛苦的事情。但是,如果您使用 XML,了解 XSLT 真的很重要。

                您需要的 XSLT 非常简单。从 XSLT identity transform 开始。然后添加一个模板,将ResponseItem 元素转换为DataSet 期望的格式:

                <xsl:template match="Response">
                   <MyDataSetName>
                      <xsl:apply-templates select="Item"/>
                   </MyDataSetName>
                </xsl:template>
                
                <xsl:template match="Item">
                   <MyDataTableName>
                      <xsl:apply-templates select="Field[@Name='ID' or @Name='Name' or @Name='Path']"/>
                   </MyDataTableName>
                </xsl:template>
                
                <xsl:template match="Field">
                   <xsl:element name="{@Name}">
                      <xsl:value-of select="."/>
                   </xsl:element>
                </xsl:template>
                

                这会将您的 XML 更改为如下所示的文档:

                <MyDataSetName>
                  <MyDataTableName>
                    <ID>767147519</ID> 
                    <Name>Music</Name> 
                    <Path>Family\Music</Path> 
                  </MyDataTableName>
                </MyDataSetName>
                

                ...您可以将其提供给DataSet.ReadXml

                编辑:

                我应该指出,除非您经常这样做,否则它并不明显,这样做的一个影响是您需要创建和填充 DataSet 的 C# 代码量是最少的:

                    private DataSet GetDataSet(string inputFilename, string transformFilename)
                    {
                        StringBuilder sb = new StringBuilder();
                        using (XmlReader xr = XmlReader.Create(inputFilename))
                        using (XmlWriter xw = XmlWriter.Create(new StringWriter(sb)))
                        {
                            XslCompiledTransform xslt = new XslCompiledTransform();
                            xslt.Load(transformFilename);
                            xslt.Transform(xr, xw);
                        }
                        using (StringReader sr = new StringReader(sb.ToString()))
                        {
                            DataSet ds = new DataSet();
                            ds.ReadXml(sr);
                            return ds;
                        }
                    }
                

                它也可以重复使用。您可以使用此方法根据需要从尽可能多的不同输入格式填充尽可能多的不同DataSets;你只需要为每种格式编写一个转换。

                【讨论】:

                • 使用 XSLT 将文档转换为 DataSet 可以读取的内容似乎有点 OTT,因为您可以使用非常简单的 Linq 或 XmlDocument 来实现同样的事情!
                • 如果您熟悉 XSLT,就像任何使用 XML 的人都应该熟悉的那样,以上内容是微不足道的。此外,这些答案中提供的“非常简单的 Linq 位”只是填充变量;它实际上并没有创建DataRow 对象,将它们添加到右侧DataTable 等。我并不是说我的答案因此更好——例如,我省略了执行转换的代码。但它确实用一个方法调用替换了所有 ADO 代码,并将所需的 C# 代码量减少到最低限度。
                • +1 我也会去加载转换后的 Xml。 Xslt 可能更简单。
                • 只需&lt;xsl:template match="Field[@Name]"&gt;,您就可以将您的xslt 用于您的源Xml 可能包含的任何&lt;Field /&gt;
                • @Colin 这个解决方案没有过度设计。以这种方式处理您的 Xml,如果您的 Xml 中的字段名称发生更改,您将不必更改任何代码。而且您无需编写任何代码即可将表和行添加到您的 DataSet。
                【解决方案10】:

                另一种匿名类型的解决方案:

                        var doc = XDocument.Load("c:\\test");
                
                        var list = doc.Root
                         .Elements("Item")
                         .Select(item =>
                          new
                          {
                              Id = item.Elements("Field").Where(e => e.Attribute("Name").Value == "ID").Select(e => e.Value).FirstOrDefault(),
                              Path = item.Elements("Field").Where(e => e.Attribute("Name").Value == "Path").Select(e => e.Value).FirstOrDefault(),
                              Name = item.Elements("Field").Where(e => e.Attribute("Name").Value == "Name").Select(e => e.Value).FirstOrDefault(),
                          })
                         .ToArray();
                
                        foreach (var item in list)
                        {
                            var id = item.Id;
                            var name = item.Name;
                        }
                

                new 运算符中的丑陋表达式可以用下一个匿名函数来缩短:

                Func<XElement, string, string> getAttrValue = (node, attrName) =>
                {
                 return node.Elements("Field")
                  .Where(e => e.Attribute("Name").Value == attrName)
                  .Select(e => e.Value)
                  .FirstOrDefault();
                };
                

                那么 new 操作符看起来像:

                 new 
                 { 
                  Id = getAttrValue(item, "ID"), 
                  Path = getAttrValue(item, "Path"),
                  Name = getAttrValue(item, "Name"),
                 }
                

                【讨论】:

                  【解决方案11】:

                  您的 linq 查询返回文档中的所有 Item 元素:

                  Dim name = From c In loaded.Descendants("Item") Select c
                  

                  下面的代码试图从 'Item' 元素中获取 'ID' 属性:

                  Dim str1 = result.Attribute("ID").Value
                  

                  但是,“ID”属性位于“字段”子元素上。

                  你需要的是:

                  // find all the Item elements
                  var items = loaded.Descendants("Item");
                  foreach(var item in items)
                  {
                    // find all the Field child elements
                    var fields = item.Descendants("Field");
                  
                    // find the field element which has an ID attribute, and obtain the element value  
                    string id = fields.Where(field => field.Attribute("ID")!=null)
                                      .Single()
                                      .Value;
                  
                    // etc ...
                  }
                  

                  【讨论】:

                    【解决方案12】:

                    我想获取 ID、Name 和 Path 的属性值。

                    如果您不介意使用 XDocument 以外的其他内容,我会使用 XmlDocument:

                            XmlDocument doc = new XmlDocument();
                            doc.Load(new XmlTextReader("XData.xml"));
                            XmlNodeList items = doc.GetElementsByTagName("Item");
                            foreach (XmlElement item in items.Cast<XmlElement>())
                            {
                                XmlElement[] fields = item.GetElementsByTagName("Field").Cast<XmlElement>().ToArray();
                                string id = (from s in fields where s.Attributes["Name"].InnerText == "ID" select s).First().InnerText;
                                string name = (from s in fields where s.Attributes["Name"].InnerText == "Name" select s).First().InnerText;
                                string path = (from s in fields where s.Attributes["Name"].InnerText == "Path" select s).First().InnerText;
                    
                                //Do stuff with data.
                            }
                    

                    就性能而言,这可能很糟糕。您还可以在字段上设置一个循环,然后在名称属性上使用一个开关,这样您就不会多次检查同一个字段。为什么你还需要任何 linq 呢?


                            XmlDocument doc = new XmlDocument();
                            doc.Load(new XmlTextReader("XData.xml"));
                            XmlNodeList items = doc.GetElementsByTagName("Item");
                            foreach (XmlElement item in items.Cast<XmlElement>())
                            {
                                foreach (XmlNode field in item.GetElementsByTagName("Field"))
                                {
                                    string name = field.Attributes["Name"].InnerText;
                                    switch (name)
                                    {
                                        case "ID":
                                            string id = field.InnerText;
                                            //Do stuff with data.
                                            break;
                                        case "Path":
                                            string path = field.InnerText;
                                            //Do stuff with data.
                                            break;
                                        case "Name":
                                            string name = field.InnerText;
                                            //Do stuff with data.
                                            break;
                                        default:
                                            break;
                                    }
                                }
                            }
                    

                    【讨论】:

                    • 您可能想要使用 Linq,因为它更简洁易读 ;-)
                    • 我更喜欢使用 Linq,有人告诉我这是实现我想要做的事情的更好方法,而且我似乎很容易掌握它,所以不想开始学习另一种方法。不过谢谢。
                    猜你喜欢
                    • 1970-01-01
                    • 2012-01-20
                    • 2012-05-29
                    • 1970-01-01
                    • 1970-01-01
                    • 2010-11-25
                    • 2011-09-05
                    • 2016-08-23
                    • 1970-01-01
                    相关资源
                    最近更新 更多