Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
273 views
in Technique[技术] by (71.8m points)

c# - How to serialize an ICollection<T> that also has read/write properties to XML

I have class that implement list of custom class. That class also has two properties. But when I serialize that class, XML contains only array of my custom classes but don't contains another two properties. Here is the class:

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;
        }
    }
}

and code that I use to serialize it:

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

and this is XML that I got:

    <?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>

I want my xml contains two properties together with a array of custom class, that I could deserialize it into its original state...

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The reason that your properties are not deserialized is explained in the documentation section Serializing a Class that Implements the ICollection Interface:

You can create your own collection classes by implementing the ICollection interface, and use the XmlSerializer to serialize instances of these classes. Note that when a class implements the ICollection interface, only the collection contained by the class is serialized. Any public properties or fields added to the class will not be serialized.

So, that's that.

You might consider changing your design so your classes do not have properties. For some reasons to make this change, see Why not inherit from List?.

If you nevertheless choose to have a collection with serializable properties, you're going to need to manually implement IXmlSerializable. This is burdensome, since you need to handle many "edge" cases including empty elements, unexpected elements, comments, and presence or absence of whitespace, all of which can throw off your ReadXml() method. For some background, see How to Implement IXmlSerializable Correctly.

First, create a base class for generic lists with serializable properties:

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
}

To use this class, you will need to override ReadCustomElement(XmlReader reader), which reads a single custom property, and WriteCustomElements(XmlWriter writer), which writes all custom properties. (Note the asymmetry, it makes implementing class hierarchies a little easier.) Then create your Porudzbina class as follows:

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);
        }
    }
}

This will create XML that looks like:

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...