【问题标题】:How to serialize an ICollection<T> that also has read/write properties to XML如何序列化具有 XML 读/写属性的 ICollection<T>
【发布时间】:2016-03-17 05:52:41
【问题描述】:

我有实现自定义类列表的类。该类也有两个属性。但是当我序列化该类时,XML 仅包含我的自定义类的数组,但不包含另外两个属性。 这是课程:

public class Porudzbina : List<PorudzbenicaStavka>, IEnumerable<SqlDataRecord>
{
    public long KomSifra { get; set; }
    public Guid KomId { get; set; }

    IEnumerator<SqlDataRecord> IEnumerable<SqlDataRecord>.GetEnumerator()
    {
        var sqlRow = new SqlDataRecord(
              new SqlMetaData("rb", SqlDbType.Int),
              new SqlMetaData("RobaSifra", SqlDbType.NVarChar, 50),
              new SqlMetaData("RobaNaziv", SqlDbType.NVarChar, 100)
             );
        foreach (PorudzbenicaStavka por in this)
        {
            sqlRow.SetInt32(0, por.rb);
            sqlRow.SetString(1, por.RobaSifra);
            sqlRow.SetString(2, por.RobaNaziv);
            yield return sqlRow;
        }
    }
}

以及我用来序列化它的代码:

    XmlSerializer serializer = new XmlSerializer(typeof(Porudzbina));
    using (TextWriter writer = new StreamWriter(@"C:\Xmle.xml"))
    {
        serializer.Serialize(writer, por);
    } 

这是我得到的 XML:

    <?xml version="1.0" encoding="utf-8"?>
<ArrayOfPorudzbenicaStavka xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <PorudzbenicaStavka>
    <rb>1</rb>
    <RobaSifra>3702</RobaSifra>
    <RobaNaziv>Foullon mlecna cokolada 33% Ecuador 100g</RobaNaziv>    
  </PorudzbenicaStavka>
  <PorudzbenicaStavka>
    <rb>2</rb>
    <RobaSifra>1182</RobaSifra>
    <RobaNaziv>IL Capitano zelena maslina sa paprikom 720g</RobaNaziv>    
  </PorudzbenicaStavka>
  <PorudzbenicaStavka>
    <rb>3</rb>
    <RobaSifra>1120</RobaSifra>
    <RobaNaziv>Kaiser tuna steak sa papricicom u ulju 170g.</RobaNaziv>    
  </PorudzbenicaStavka>
</ArrayOfPorudzbenicaStavka>

我希望我的 xml 包含两个属性以及一组自定义类,我可以将其反序列化为其原始状态...

【问题讨论】:

    标签: c# serialization xml-serialization xmlserializer


    【解决方案1】:

    文档部分Serializing a Class that Implements the ICollection Interface解释了您的属性未反序列化的原因:

    您可以通过实现 ICollection 接口来创建自己的集合类,并使用 XmlSerializer 序列化这些类的实例。请注意,当一个类实现 ICollection 接口时,只有该类包含的集合被序列化。 任何添加到类的公共属性或字段都不会被序列化。

    所以,就是这样。

    您可能会考虑更改您的设计,使您的类没有属性。有关进行此更改的某些原因,请参阅Why not inherit from List?

    如果您仍然选择具有可序列化属性的集合,您将需要manually implement IXmlSerializable。这是繁重的,因为您需要处理许多“边缘”情况,包括空元素、意外元素、cmets 以及是否存在空格,所有这些都可能导致您的 ReadXml() 方法失效。有关一些背景信息,请参阅How to Implement IXmlSerializable Correctly

    首先,为具有可序列化属性的通用列表创建一个基类:

    public class XmlSerializableList<T> : List<T>, IXmlSerializable where T : new()
    {
        public XmlSerializableList() : base() { }
    
        public XmlSerializableList(IEnumerable<T> collection) : base(collection) { }
    
        public XmlSerializableList(int capacity) : base(capacity) { }
    
        #region IXmlSerializable Members
    
        const string CollectionItemsName = "Items";
        const string CollectionPropertiesName = "Properties";
    
        void IXmlSerializable.WriteXml(XmlWriter writer)
        {
            // Do not write the wrapper element.
    
            // Serialize the collection.
            WriteCollectionElements(writer);
    
            // Serialize custom properties.
            writer.WriteStartElement(CollectionPropertiesName);
            WriteCustomElements(writer);
            writer.WriteEndElement();
    
            // Do not end the wrapper element.
        }
    
        private void WriteCollectionElements(XmlWriter writer)
        {
            if (Count < 1)
                return;
            // Serialize the collection.
            writer.WriteStartElement(CollectionItemsName);
    
            var serializer = new XmlSerializer(typeof(T));
            var ns = new XmlSerializerNamespaces();
            ns.Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
            foreach (var item in this)
            {
                serializer.Serialize(writer, item, ns);
            }
    
            writer.WriteEndElement();
        }
    
        /// <summary>
        /// Write ALL custom elements to the XmlReader
        /// </summary>
        /// <param name="writer"></param>
        protected virtual void WriteCustomElements(XmlWriter writer)
        {
        }
    
        void IXmlSerializable.ReadXml(XmlReader reader)
        {
            if (reader.IsEmptyElement)
            {
                reader.Read();
                return;
            }
            reader.ReadStartElement(); // Advance to the first sub element of the wrapper element.
            while (reader.NodeType != XmlNodeType.EndElement)
            {
                if (reader.NodeType != XmlNodeType.Element)
                    // Comment, whitespace
                    reader.Read();
                else if (reader.IsEmptyElement)
                    reader.Read();
                else if (reader.Name == CollectionItemsName)
                    ReadCollectionElements(reader);
                else if (reader.Name == CollectionPropertiesName)
                    ReadCustomElements(reader);
                else
                    // Unknown element, skip it.
                    reader.Skip();
            }
    
            // Move past the end of the wrapper element
            reader.ReadEndElement();
        }
    
        void ReadCustomElements(XmlReader reader)
        {
            reader.ReadStartElement(); // Advance to the first sub element of the collection element.
            while (reader.NodeType != XmlNodeType.EndElement)
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    using (var subReader = reader.ReadSubtree())
                    {
                        while (subReader.NodeType != XmlNodeType.Element) // Read past XmlNodeType.None
                            if (!subReader.Read())
                                break;
                        ReadCustomElement(subReader);
                    }
                }
                reader.Read();
            }
            // Move past the end of the properties element
            reader.Read();
        }
    
        void ReadCollectionElements(XmlReader reader)
        {
            var serializer = new XmlSerializer(typeof(T));
            reader.ReadStartElement(); // Advance to the first sub element of the collection element.
            while (reader.NodeType != XmlNodeType.EndElement)
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    using (var subReader = reader.ReadSubtree())
                    {
                        while (subReader.NodeType != XmlNodeType.Element) // Read past XmlNodeType.None
                            if (!subReader.Read())
                                break;
                        var item = (T)serializer.Deserialize(subReader);
                        Add(item);
                    }
                }
                reader.Read();
            }
            // Move past the end of the collection element
            reader.Read();
        }
    
        /// <summary>
        /// Read ONE custom element from the XmlReader
        /// </summary>
        /// <param name="reader"></param>
        protected virtual void ReadCustomElement(XmlReader reader)
        {
        }
    
        XmlSchema IXmlSerializable.GetSchema()
        {
            return null;
        }
    
        #endregion
    }
    

    要使用这个类,您需要覆盖ReadCustomElement(XmlReader reader),它读取一个单个自定义属性,以及WriteCustomElements(XmlWriter writer),它写入所有自定义属性。 (注意不对称,它使实现类层次结构更容易一些。)然后按如下方式创建您的 Porudzbina 类:

    public class Porudzbina : XmlSerializableList<PorudzbenicaStavka>
    {
        public long KomSifra { get; set; }
        public Guid KomId { get; set; }
    
        const string KomSifraName = "KomSifra";
        const string KomIdName = "KomId";
    
        protected override void WriteCustomElements(XmlWriter writer)
        {
            writer.WriteElementString(KomSifraName, XmlConvert.ToString(KomSifra));
            writer.WriteElementString(KomIdName, XmlConvert.ToString(KomId));
            base.WriteCustomElements(writer);
        }
    
        protected override void ReadCustomElement(XmlReader reader)
        {
            if (reader.Name == KomSifraName)
            {
                KomSifra = reader.ReadElementContentAsLong();
            }
            else if (reader.Name == KomIdName)
            {
                var s = reader.ReadElementContentAsString();
                KomId = XmlConvert.ToGuid(s);
            }
            else
            {
                base.ReadCustomElement(reader);
            }
        }
    }
    

    这将创建如下所示的 XML:

    <Porudzbina>
        <Items>
            <PorudzbenicaStavka>
                <!-- contents of first PorudzbenicaStavka -->
            </PorudzbenicaStavka>
            <!-- Additional PorudzbenicaStavka -->
        </Items>
        <Properties>
            <KomSifra>101</KomSifra>
            <KomId>bb23a3b8-23d3-4edd-848b-d7621e6ed2c0</KomId>
        </Properties>
    </Porudzbina>
    

    【讨论】:

      【解决方案2】:

      我已将我的序列化库上传到 Github,在那里处理此类问题。

      Atlas Xml Serializer

      我假设您有以下数据类。我刚刚在属性上添加了 [XmlElement] 属性,以强制它们序列化为 xml 元素。

      public class Porudzbina : List<PorudzbenicaStavka>, IEnumerable<SqlDataRecord>
      {
          [XmlElement]
          public long KomSifra { get; set; }
      
          [XmlElement]
          public Guid KomId { get; set; }
      
          IEnumerator<SqlDataRecord> IEnumerable<SqlDataRecord>.GetEnumerator()
          {
              var sqlRow = new SqlDataRecord(
                    new SqlMetaData("rb", SqlDbType.Int),
                    new SqlMetaData("RobaSifra", SqlDbType.NVarChar, 50),
                    new SqlMetaData("RobaNaziv", SqlDbType.NVarChar, 100)
                   );
              foreach (PorudzbenicaStavka por in this)
              {
                  sqlRow.SetInt32(0, por.rb);
                  sqlRow.SetString(1, por.RobaSifra);
                  sqlRow.SetString(2, por.RobaNaziv);
                  yield return sqlRow;
              }
          }
      }
      
      public class PorudzbenicaStavka
      {
          [XmlElement]
          public int rb { get; set; }
      
          [XmlElement]
          public string RobaSifra { get; set; }
      
          [XmlElement]
          public string RobaNaziv { get; set;  }
      }
      

      下面是实例:

      var o = new Porudzbina
      {
          new PorudzbenicaStavka { rb=1, RobaSifra="3702", RobaNaziv="Foullon mlecna cokolada 33% Ecuador 100g" },
          new PorudzbenicaStavka { rb=2, RobaSifra="1182", RobaNaziv="IL Capitano zelena maslina sa paprikom 720g" },
          new PorudzbenicaStavka { rb=3, RobaSifra="1120", RobaNaziv="Kaiser tuna steak sa papricicom u ulju 170g." },
      };
      
      o.KomId = new Guid("{EC63AEC3-1512-451F-B967-836DD0E9820A}");
      o.KomSifra = 999999;
      

      atlas xml 序列化库是如何完成这项工作的:

      var serialized = Atlas.Xml.Serializer.Serialize(o, true);
      var deserialized = Atlas.Xml.Serializer.Deserialize<Porudzbina>(serialized);
      

      Xml 看起来像这样:

      <Porudzbina>
        <KomSifra>999999</KomSifra>
        <KomId>ec63aec3-1512-451f-b967-836dd0e9820a</KomId>
        <item>
          <rb>1</rb>
          <RobaSifra>3702</RobaSifra>
          <RobaNaziv>Foullon mlecna cokolada 33% Ecuador 100g</RobaNaziv>
        </item>
        <item>
          <rb>2</rb>
          <RobaSifra>1182</RobaSifra>
          <RobaNaziv>IL Capitano zelena maslina sa paprikom 720g</RobaNaziv>
        </item>
        <item>
          <rb>3</rb>
          <RobaSifra>1120</RobaSifra>
          <RobaNaziv>Kaiser tuna steak sa papricicom u ulju 170g.</RobaNaziv>
        </item>
      </Porudzbina>
      

      如果你像这样改变数据类:

      [Atlas.Xml.XmlSerializationType(ChildElementName = "PorudzbenicaStavka")]
      public class Porudzbina : List<PorudzbenicaStavka>, IEnumerable<SqlDataRecord>
      {
          public long KomSifra { get; set; }
          public Guid KomId { get; set; }
          // ...
      }
      
      public class PorudzbenicaStavka
      {
          public int rb { get; set; }
          public string RobaSifra { get; set; }
      
          [XmlText]
          public string RobaNaziv { get; set;  }
      }
      

      那么,序列化的类应该是这样的:

      <Porudzbina KomSifra="999999" KomId="ec63aec3-1512-451f-b967-836dd0e9820a">
        <PorudzbenicaStavka rb="1" RobaSifra="3702">Foullon mlecna cokolada 33% Ecuador 100g</PorudzbenicaStavka>
        <PorudzbenicaStavka rb="2" RobaSifra="1182">IL Capitano zelena maslina sa paprikom 720g</PorudzbenicaStavka>
        <PorudzbenicaStavka rb="3" RobaSifra="1120">Kaiser tuna steak sa papricicom u ulju 170g.</PorudzbenicaStavka>
      </Porudzbina>
      

      【讨论】:

        猜你喜欢
        • 2023-03-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-11-22
        • 1970-01-01
        • 2011-07-16
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多