如何序列化ICollection <t>,它也具有XML的读/写属性

时间:2015-12-11 10:37:03

标签: c# serialization xml-serialization xmlserializer

我有一个实现自定义类列表的类。该类还有两个属性。但是当我序列化该类时,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包含两个属性以及一个自定义类数组,我可以将其反序列化为原始状态...

2 个答案:

答案 0 :(得分:1)

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

  

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

那就是那个。

您可以考虑更改设计,以便您的课程没有属性。出于某些原因进行此更改,请参阅Why not inherit from List?

如果您仍然选择具有可序列化属性的集合,则需要manually implement IXmlSerializable。这很麻烦,因为您需要处理许多“边缘”情况,包括空元素,意外元素,注释以及是否存在空格,所有这些都会导致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>

答案 1 :(得分:0)

我已将序列化库上传到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>