31

这是我要解决的问题的一个虚构示例。如果我在 C# 中工作,并且有这样的 XML:

<?xml version="1.0" encoding="utf-8"?>
<Cars>
  <Car>
    <StockNumber>1020</StockNumber>
    <Make>Nissan</Make>
    <Model>Sentra</Model>
  </Car>
  <Car>
    <StockNumber>1010</StockNumber>
    <Make>Toyota</Make>
    <Model>Corolla</Model>
  </Car>
  <SalesPerson>
    <Company>Acme Sales</Company>
    <Position>
       <Salary>
          <Amount>1000</Amount>
          <Unit>Dollars</Unit>
    ... and on... and on....
  </SalesPerson>
</Cars>

SalesPerson 中的 XML 可能非常长,大小为兆字节。我想反序列化标记,不反序列化 SalesPerson XML 元素,而是将其保持为原始形式“供以后使用”。

本质上,我希望能够将其用作 XML 的对象表示。

[System.Xml.Serialization.XmlRootAttribute("Cars", Namespace = "", IsNullable = false)]
public class Cars
{
    [XmlArrayItem(typeof(Car))]
    public Car[] Car { get; set; }

    public Stream SalesPerson { get; set; }
}

public class Car
{
    [System.Xml.Serialization.XmlElementAttribute("StockNumber")]
    public string StockNumber{ get; set; }

    [System.Xml.Serialization.XmlElementAttribute("Make")]
    public string Make{ get; set; }

    [System.Xml.Serialization.XmlElementAttribute("Model")]
    public string Model{ get; set; }
}

其中,Cars 对象上的 SalesPerson 属性将包含一个流,其中包含在通过 XmlSerializer 运行后位于 <SalesPerson> xml 元素中的原始 xml。

这可以做到吗?我可以选择仅反序列化 xml 文档的“一部分”吗?

谢谢!-麦克风

ps 示例 xml 从How to Deserialize XML 文档中窃取

4

9 回答 9

42

这可能是一个有点旧的线程,但无论如何我都会发布。我遇到了同样的问题(需要从一个超过 1MB 的文件中反序列化 10kb 的数据)。在主对象(它有一个需要反序列化器的 InnerObject)中,我实现了一个 IXmlSerializable 接口,然后更改了 ReadXml 方法。

我们有 xmlTextReader 作为输入,第一行是读取到一个 XML 标记:

reader.ReadToDescendant("InnerObjectTag"); //tag which matches the InnerObject

然后为我们要反序列化的对象类型创建 XMLSerializer 并对其进行反序列化

XmlSerializer   serializer = new XmlSerializer(typeof(InnerObject));

this.innerObject = serializer.Deserialize(reader.ReadSubtree()); //this gives serializer the part of XML that is for  the innerObject data

reader.close(); //now skip the rest 

这节省了我大量反序列化的时间,并允许我只读取 XML 的一部分(只是描述文件的一些细节,这可能有助于用户确定文件是否是他想要加载的文件)。

于 2010-02-12T11:53:47.950 回答
7

user271807接受的答案是一个很好的解决方案,但我发现,我还需要设置片段的 xml 根,以避免出现内部异常的异常,如下所示:

...xmlns=''> was not expected

当我尝试仅反序列化此 xml 文档的内部 Authentication 元素时,出现了此异常:

<?xml version=""1.0"" encoding=""UTF-8""?>
<Api>
  <Authentication>                       
      <sessionid>xxx</sessionid>
      <errormessage>xxx</errormessage>                
  </Authentication>
</ApI>

所以我最终创建了这个扩展方法作为一个可重用的解决方案- 警告包含内存泄漏,见下文:

public static T DeserializeXml<T>(this string @this, string innerStartTag = null)
        {
            using (var stringReader = new StringReader(@this))
            using (var xmlReader = XmlReader.Create(stringReader)) {
                if (innerStartTag != null) {
                    xmlReader.ReadToDescendant(innerStartTag);
                    var xmlSerializer = new XmlSerializer(typeof(T), new XmlRootAttribute(innerStartTag));
                    return (T)xmlSerializer.Deserialize(xmlReader.ReadSubtree());
                }
                return (T)new XmlSerializer(typeof(T)).Deserialize(xmlReader);
            }
        }

2017 年 3 月 20 日更新:正如下面的评论所指出的,使用 XmlSerializer 的构造函数之一时存在内存泄漏问题,因此我最终使用了如下所示的缓存解决方案:

    /// <summary>
    ///     Deserialize XML string, optionally only an inner fragment of the XML, as specified by the innerStartTag parameter.
    /// </summary>
    public static T DeserializeXml<T>(this string @this, string innerStartTag = null) {
        using (var stringReader = new StringReader(@this)) {
            using (var xmlReader = XmlReader.Create(stringReader)) {
                if (innerStartTag != null) {
                    xmlReader.ReadToDescendant(innerStartTag);
                    var xmlSerializer = CachingXmlSerializerFactory.Create(typeof (T), new XmlRootAttribute(innerStartTag));
                    return (T) xmlSerializer.Deserialize(xmlReader.ReadSubtree());
                }
                return (T) CachingXmlSerializerFactory.Create(typeof (T), new XmlRootAttribute("AutochartistAPI")).Deserialize(xmlReader);
            }
        }
    }
/// <summary>
///     A caching factory to avoid memory leaks in the XmlSerializer class.
/// See http://dotnetcodebox.blogspot.dk/2013/01/xmlserializer-class-may-result-in.html
/// </summary>
public static class CachingXmlSerializerFactory {
    private static readonly ConcurrentDictionary<string, XmlSerializer> Cache = new ConcurrentDictionary<string, XmlSerializer>();
    public static XmlSerializer Create(Type type, XmlRootAttribute root) {
        if (type == null) {
            throw new ArgumentNullException(nameof(type));
        }
        if (root == null) {
            throw new ArgumentNullException(nameof(root));
        }
        var key = string.Format(CultureInfo.InvariantCulture, "{0}:{1}", type, root.ElementName);
        return Cache.GetOrAdd(key, _ => new XmlSerializer(type, root));
    }
    public static XmlSerializer Create<T>(XmlRootAttribute root) {
        return Create(typeof (T), root);
    }
    public static XmlSerializer Create<T>() {
        return Create(typeof (T));
    }
    public static XmlSerializer Create<T>(string defaultNamespace) {
        return Create(typeof (T), defaultNamespace);
    }
    public static XmlSerializer Create(Type type) {
        return new XmlSerializer(type);
    }
    public static XmlSerializer Create(Type type, string defaultNamespace) {
        return new XmlSerializer(type, defaultNamespace);
    }
}
于 2016-04-14T07:05:00.710 回答
3

您可以通过在类中实现 ISerializable 接口来控制序列化的完成方式。请注意,这也意味着具有方法签名(SerializationInfo 信息,StreamingContext 上下文)的构造函数,并确保您可以按照您的要求进行操作。

但是,请仔细查看您是否真的需要使用流式传输来执行此操作,因为如果您不必使用流式传输机制,那么使用 Linq to XML 实现相同的事情会更容易,并且更容易长期维护术语(海事组织)

于 2008-12-15T21:58:12.883 回答
2

我认为前面的评论者在他的评论中是正确的,即 XML 可能不是这里后备存储的最佳选择。

如果您遇到规模问题并且没有利用 XML 获得的其他一些优点,例如转换,那么最好使用数据库来存储您的数据。您所做的操作似乎更适合该模型。

我知道这并不能真正回答您的问题,但我想我会强调您可能使用的替代解决方案。一个好的数据库和适当的 OR 映射器(如 .netTiers、NHibernate 或最近的 LINQ to SQL / Entity Framework)可能会让您在对代码库的其余部分进行最小更改的情况下恢复并运行。

于 2008-12-15T22:57:29.237 回答
1

通常 XML 反序列化是一个开箱即用的全有或全无的命题,因此您可能需要自定义。如果您不进行完全反序列化,您将面临 xml 在 SalesPerson 元素中格式错误的风险,因此文档无效。

如果您愿意接受这种风险,您可能需要进行一些基本的文本解析,以使用纯文本处理工具将 SalesPerson 元素分解为不同的文档,然后处理 XML。

这是一个很好的例子,说明了为什么 XML 并不总是正确的答案。

于 2008-12-15T21:58:21.540 回答
1

请尝试将 SalesPerson 属性定义为 type XmlElement。这适用于使用 XML 序列化的 ASMX Web 服务的输出。我认为它也适用于输入。我希望整个<SalesPerson>元素都以XmlElement.

于 2009-07-27T18:42:36.930 回答
0

您可以通过在 Cars 类上实现IXmlSerializable接口来控制对 Cars 类的哪些部分进行反序列化,然后在ReadXml(XmlReader)方法中读取和反序列化 Car 元素,但是当您到达 SalesPerson 元素时,您将读取它的子树作为一个字符串,然后使用 StreamWriter 在文本内容上构造一个 Stream。

如果您从不希望 XmlSerializer 写出 SalesPerson 元素,请使用 [XmlIgnore] 属性。当您将 Cars 类序列化为其 XML 表示时,我不确定您想要发生什么。您是否试图仅阻止 SalesPerson 的反序列化,同时仍然能够序列化由 Stream 表示的 SalesPerson 的 XML 表示?

如果你想要一个具体的实现,我可能会提供一个代码示例。

于 2008-12-15T23:39:42.957 回答
0

如果您只想解析 SalesPerson 元素但将其保留为字符串,则应使用 Xsl Transform 而不是“反序列化”。另一方面,如果您想解析 SalesPerson 元素并仅从所有其他非 SalesPerson 元素填充内存中的对象,那么 Xsl Transform 也可能是要走的路。如果文件很大,您可以考虑将它们分开并使用 Xsl 组合不同的 xml 文件,以便销售人员 I/O 仅在您需要时发生。

于 2009-07-27T18:40:07.293 回答
0

我建议您使用任何轻量级方法(如 XmlReader、XPathDocument 或 LINQ-to-XML)手动读取 Xml。

当您只需要读取 3 个属性时,我想您可以编写从该节点手动读取的代码,并完全控制它的执行方式,而不是依赖于序列化/反序列化

于 2009-08-06T10:39:43.397 回答