.NET XmlSerializer and multiple references to the same object
Asked Answered
C

5

8

My repository has List<Student>, List<Course> and List<Enrolment> where an Enrolment has Enrolment.Student and Enrolment.Course which are references one of the students or courses in the two previous lists.

When I use XmlSerializer on my repository it outputs redundant data as it serializes all properties of each student in List<Student> then again for every reference to those same students in List<Enrolment>. I'm looking for an elegant way to solve this.

After deserialization I can fix the references using the ID values in the duplicate object instances created by the deserialization but this seems hackish.

One method to fix the redundant output is to XmlIgnore Enrolment.Student and Enrolment.Course and create two more properties for serialization - Enrolment.StudentID and Enrolment.CourseID. However during deserialization, the references for Enrolment.Student and Enrolment.Course cannot be set (AFAIK) since the results of deserialization of List<Student> and List<Course> are not available.

Another method I thought of is to serialize lower down in my object hierarchy doing each of my Lists separately and controlling the order of deserialization - I rather not do this.

Another method would be to XmlIgnore List<Enrolment> and create an enrolment serialization helper class that initializes List<Enrolment> after the deserialization of itself is complete. This seems like a lot of effort.

How do other people serialize/deserialize multiple references to the same object using XmlSerializer?

Convoluted answered 8/1, 2010 at 8:13 Comment(0)
S
3

Oh the pains of serialization :-> ...

There was never a generic solution for this, I guess that's why MS stripped it out of the Silverlight framework.

I never rely on any automatic serialization mechanisms of the .net framework. For my own models and repositories, I usually know or can easily programmatically determine which properties are simple scalar ones (numbers/strings/etc) and which are links to other objects (as well as which are lists of either).

There are basically 2 scenarios:

1: We want to serialize/transfer only the flat information of objects. In that case I transfer only the respective IDs for properties that link to other objects. The receiver can then make subsequent queries to get all other objects they need.

2: We want to transfer as much information as possible, i.e. deeper nested XML with several levels, mostly for some reporting functionality displaying everything directly using merely some CSS on the XML. In that case, it is actually desired that objects that are the same will be resolved multiple times into the XML tree.

Sometimes I need to tweak the first scenario a little bit in order to avoid too many subsequent query calls, but usually I get along very well. I.e. I have built into our code base that we can specify which additional objects we want to resolve when, and/or it's configured somewhere.

Simmonds answered 8/1, 2010 at 11:25 Comment(1)
Very reassuring - Being new to this I assumed I must be missing something. Since my problem pertains to your first scenario I will follow your suggestion of building something into the code base to tidy up the deserialization. Maybe I'll create an IXmlFinalizeDeserialization that can be called across all my objects, tracking those whose deserialization is inadequate and fixes the references.Convoluted
A
2

There is no solution for this issue using the XML Serializer. It does not have a concept of identity that it might use to remove duplication.

The best you can do is to serialize the pool of objects separately from their references. You could then recreate your lists after deserialization.

BTW, are you aware that the XmlSerializer is not specific to C#?

Alasdair answered 8/1, 2010 at 8:22 Comment(1)
Thanks John - very succinct. I've removed c# from my tags.Convoluted
W
2

You can implement interface IXmlSerializable to Enrolment and in WriteXml method generate student and course XML which will contains only keys e.g.:

<Student Id="5"/>
<Course Id="6"/>

and in ReadXml method you can load references from this. You must also set XmlIgnore attribute to Student and Course property.

Wengert answered 8/1, 2010 at 8:32 Comment(1)
The trouble with this approach is that during ReadXml you can't load references (AFAIK) to other data also being deserialized because it is unavailable until the deserialization is complete. Do you know someway around this?Convoluted
C
0

How does this sound as a solution:

  1. XMLIgnore each secondary reference ie Enrolment.Student & Enrolment.Course
  2. create a property for each secondary reference that is used to serialize/deserialize a foreign key for that reference instead - Prefix with XML_FK. eg XML_FK_Student & XML_FK_Course
  3. Create a method XML_FinalizeDeserialization that is called after deserialization to load the references using those foreign key properties.
Convoluted answered 8/1, 2010 at 14:30 Comment(2)
Wouldn't it be nice if we could create an attribute [XmlSecondary] we put ahead of a reference that causes the XML_Serializer to inspect the reference for fields prefixed with [XMLPK] and only output those? Possible?Convoluted
Quite possible, but you'd then need to write your own serializer to recognize the attribute, and I think you don't want to do that.Alasdair
E
0

You should/can use Reference Tracking with the datacontract serializer:

//deserilaize:
using(MemoryStream memStmBack = new MemoryStream()) {
  var serializerForth = new DataContractSerializer(
    typeof(YourType),
    null,
    0x7FFF /*maxItemsInObjectGraph*/ ,
    false /*ignoreExtensionDataObject*/ ,
    true /*preserveObjectReferences*/ ,
    null /*dataContractSurrogate*/ );

  byte[] data = System.Text.Encoding.UTF8.GetBytes(xml);
  memStmBack.Write(data, 0, data.Length);
  memStmBack.Position = 0;
  var lsBack = (YourType) serializerForth.ReadObject(memStmBack);

}
//serialize...
using(MemoryStream memStm = new MemoryStream()) {
    var serializer = new DataContractSerializer(
      typeof(YourType),
      knownTypes,
      0x7FFF /*maxItemsInObjectGraph*/ ,
      false /*ignoreExtensionDataObject*/ ,
      true /*preserveObjectReferences*/ ,
      null /*dataContractSurrogate*/ );

    serializer.WriteObject(memStm, yourType);

    memStm.Seek(0, SeekOrigin.Begin);

    using(var streamReader = new StreamReader(memStm)) {
        result = streamReader.ReadToEnd();

Or use

[Serializable]
[DataContract(IsReference = true)]
Entremets answered 29/8, 2018 at 9:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.