【问题标题】:XmlReader read continuallyXmlReader 不断读取
【发布时间】:2015-06-25 23:09:14
【问题描述】:

我有一个非常大的 xml 文件。这是xml格式的简化版。

<?xml version='1.0' encoding='UTF-8'?>
<Sender>
 <SenderID>571099948</SenderID>
 <Sponsors>
  <Sponsor>
    <SponsorID>TEST01</SponsorID>
    <Contracts>
      <Contract>
        <ContractID>000001</ContractID>
        <Member>
          <SSN>1111111111</SSN>
          <Gender>M</Gender>
          <Benefits>
            <Benefit BenefitType="AAA">
            </Benefit>
            <Benefit BenefitType="BBB">
            </Benefit>
          </Benefits>
        </Member>
        <Member>
          <SSN>4444444444</SSN>
          <Gender>F</Gender>
          <Benefits>
            <Benefit BenefitType="AAA">
            </Benefit>
          </Benefits>
        </Member>
      </Contract>
      <Contract>
        <ContractID>0000002</ContractID>
        <Member>
          <SSN>2222222222</SSN>
          <Gender>F</Gender>
          <Benefits>
            <Benefit BenefitType="CCC">
            </Benefit>
            <Benefit BenefitType="DDD">
            </Benefit>
          </Benefits>
        </Member>
      </Contract>
      <Contract>
        <ContractID>0000003</ContractID>
        <Member>
          <SSN>333333333</SSN>
          <Gender>F</Gender>
          <Benefits> 
            <Benefit BenefitType="CCC">
            </Benefit>
          </Benefits>
        </Member>
      </Contract>
    </Contracts>
  </Sponsor>
  <Sponsor>
    <SponsorID>TEST02</SponsorID>
    <Contracts>
      <Contract>
        <ContractID>0000011</ContractID>
        <Member>
          <SSN>1111111111</SSN>
          <Gender>M</Gender>
          <Benefits>
          </Benefits>
        </Member>
      </Contract>
      <Contract>
        <ContractID>0000002</ContractID>
        <Member>
          <SSN>2222222222</SSN>
          <Gender>F</Gender>
          <Benefits>
          </Benefits>
        </Member>
      </Contract>
    </Contracts>
  </Sponsor>
</Sponsors>
</Sender>

我想从父节点获取合约节点的所有信息,以及 SponsorID。下面是使用 XmlReader 部分读取 xml 文件的代码:

        static IEnumerable<XElement> SimpleStreamAxis(string inputUrl, string elementName)      
    {

            using (XmlReader reader = XmlReader.Create(inputUrl))
            {
                reader.MoveToContent();
                while (reader.Read())
                {
                    if (reader.NodeType == XmlNodeType.Element)
                    {
                        if (reader.Name == elementName)
                        {
                            XElement el = XNode.ReadFrom(reader) as XElement;
                            if (el != null)
                            {
                                yield return el;
                            }
                        }
                    }
                }
            }                  
    }

这就是问题所在。我不能使用这个,因为整个赞助商树可能对内存来说太大了。

var sponsor = SimpleStreamAxis(file, "Sponsor");

我也不能使用这个,因为我不能只用合约节点信息告诉 SponsorID。

var contract = SimpleStreamAxis(file, "Contract");

有没有办法可以读取Sponsor中的SponsorID,向前移动光标,读取这个Sponsor下的所有Contract节点,然后移动到下一个Sponsor读取SponsorID及其Contract节点等等?

【问题讨论】:

    标签: c# xml xmlreader


    【解决方案1】:

    试试这个:

    using (XmlReader xmlReader = XmlReader.Create("file.xml"))
    {
        while (xmlReader.Read())
        {
            if (xmlReader.ReadToFollowing("SponsorID"))
            {
                string sponsorId = xmlReader.ReadElementContentAsString();
    
                // process SponsorID
                Console.WriteLine(sponsorId);
    
                if (xmlReader.ReadToFollowing("Contract"))
                {
                    do
                    {
                        XmlReader contractSubtree = xmlReader.ReadSubtree();
                        XElement contractElement = XElement.Load(contractSubtree);
    
                        // process Contract
                        Console.WriteLine(contractElement.Element("ContractID"));
    
                    } while (xmlReader.ReadToNextSibling("Contract"));
                }
            }
        }
    }
    

    【讨论】:

    • 您需要在推进外部阅读器之前处置contractSubtree,如docs 中所述:在关闭新阅读器之前,您不应对原始阅读器执行任何操作。此操作不受支持,可能会导致不可预知的行为。
    【解决方案2】:

    是的,这可以做到假设SponsorID 总是在Contract 节点之前

    基本思想是通读 XML 文件,直到找到具有所需名称 "SponsorID""Contract" 的元素,然后将它们生成以进行更高的处理

        public static IEnumerable<XElement> StreamNamedElements(XmlReader reader, IEnumerable<XName> names)
        {
            var nameSet = new HashSet<XName>(names);
    
            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element && nameSet.Contains(XName.Get(reader.LocalName, reader.NamespaceURI)))
                {
                    XElement el = XNode.ReadFrom(reader) as XElement;
                    if (el != null)
                        yield return el;
                }
            }
        }
    

    如果SponsorID 始终存在并且在Contract 之前,这将正确枚举这些元素。但是,如果某个赞助商 ID 丢失或乱序,则可能会获取之前赞助商的赞助商 ID。可以通过使用 ReadSubtree() 将每个“SponsorID”的范围限制为包含“Sponsor”元素的范围来捕获此错误:

        public static IEnumerable<XmlReader> StreamNamedSubtrees(XmlReader reader, IEnumerable<XName> names)
        {
            var nameSet = new HashSet<XName>(names);
    
            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element && nameSet.Contains(XName.Get(reader.LocalName, reader.NamespaceURI)))
                {
                    var subReader = reader.ReadSubtree();
                    yield return subReader;
                    ((IDisposable)subReader).Dispose(); // Be sure to advance to the end of the subtree if the caller did not.
                }
            }
        }
    

    然后像这样使用它:

            using (var sr = new StringReader(xml))
            using (var reader = XmlReader.Create(sr))
            {
                foreach (var subReader in StreamNamedSubtrees(reader, new[] { (XName)"Sponsor" }))
                {
                    XElement sponsorID = null;
                    foreach (var el in StreamNamedElements(subReader, new[] { (XName)"SponsorID", (XName)"Contract" }))
                    {
                        if (el.Name == "SponsorID")
                        {
                            sponsorID = el;
                        }
                        else if (el.Name == "Contract")
                        {
                            if (sponsorID == null)
                                throw new InvalidOperationException();
                            // Example "higher processing"
                            Debug.WriteLine(string.Format("{0}: {1}", sponsorID.Value, el.ToString()));
                        }
                    }
                }
            }
    

    【讨论】:

    • 谢谢!使用字典保存赞助商ID 的问题在于,当赞助商ID 更改时,它总是会产生额外的回报,包括新的赞助商ID 和旧的合同。
    • @seattleSummer - 已更新答案以解决您发现的问题。删除字典实际上使它更简单。
    • 我看不出使用这个循环的意义。 foreach (var subReader in StreamNamedSubtrees(reader, new[] { (XName)"Sponsor" }))
    • 我看不到使用 subReader 循环的意义。这可能是一个错字。我想你可能是说内部循环使用 subReader 而不是 reader
    猜你喜欢
    • 2018-05-09
    • 1970-01-01
    • 2011-05-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多