This project is read-only.

Circular Serializer

The circular serializer has two objectives:
  • Allow for serializing graphs of objects that might have relations to eachother. In other words: solve the circular reference problem
  • Work together with PostSharp4EF and Editable Business Objects to serialize 'original values'. This way, change tracking is possible on all clients and efficient attaching to EF contexts

Serializing graphs

A more detailed look at this is available here http://www.sitechno.com/Blog/CircularReferencesWithWCFSolvedADifferentWay.aspx and here http://www.sitechno.com/Blog/RevisitingSerializingCircularReferencesKeepingContextDuringDeserialization.aspx.

Given a domain object Employee:
    [DataContract(Namespace = "myNamespace", Name = "Employee")]
    public class Employee : Person
    {
        [DataMember]
        public ICollection<Employee> TeamMembers { get; set; }

        [DataMember]
        public Employee TeamLeader { get; set; }
    }

When serializing this object using WCF with anything other than the nettcp binding, you might run into a stackoverflow. This is caused by a circular reference between employees: EmployeeA has a collection of teamMembers that contain EmployeeB, while EmployeeB has point to EmployeeA through it's TeamLeader reference. WCF can not serialize this relationship.
It is possible to set a switch on the DataContractSerializer that will enable a custom serializing scheme which might look like this:
<Person xmlns:i="http://www.w3.org/2001/XMLSchema-instance" z:Id="1" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" xmlns="myNamespace">
    <FieldedNumbers xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays" z:Id="2" z:Size="2">
        <d2p1:int>12</d2p1:int>
        <d2p1:int>24</d2p1:int>
    </FieldedNumbers>
    <FirstName z:Id="3">Ruurd</FirstName>
    <LastName z:Id="4">Boeke</LastName>
    <Numbers xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays" z:Id="5" z:Size="2">
        <d2p1:int>31</d2p1:int>
        <d2p1:int>29</d2p1:int>
    </Numbers>
    <Orders z:Id="6" z:Size="2">
        <Order z:Id="7">
            <Amount>12</Amount>
            <Customer z:Ref="1" i:nil="true" />
            <OrderID>12</OrderID>
            <ProductID>19</ProductID>
        </Order>
        <Order z:Id="8">
            <Amount>12</Amount>
            <Customer z:Ref="1" i:nil="true" />
            <OrderID>13</OrderID>
            <ProductID>255</ProductID>
        </Order>
    </Orders>
    <PersonID>23</PersonID>
    <Something>666</Something>
</Person>

Here you can see the z:ID and z:Ref attributes placed on the graph. This is not interoperable.

By placing a simple attribute on your class:
[CreateSerializeSurrogate("EditableBusinessObjects.Postsharp.Public.IEditableBusinessObject, EditableBusinessObjects.Postsharp.Public")]
this problem can be fixed.

The attribute triggers the following changes:
  • Create a surrogate class that has all the same datamembers as the original class
  • Create a constructor that will check a List<object> to see if the object was already serialized.
    • If not: all properties are copied
    • If so: only an ID is set
  • Create a method inside the original type, that will copy all information from the surrogate to itself (no reflection is used)

It is important to understand that this is never visible in Visual Studio. Your code remains untouched. The resulting dll is enhanced with these features

Example

Using reflector we can take a look at the Person class:
[EdmEntityType(Name="Person", NamespaceName="Domain")]
public class Person : IPocoFacade, IComposed<IPocoFacade>, IUpdateComplexTypesWithTracker, IStorageAccess, IRelationshipLoader, ISerializationHelp
{

.... members left out ...
 
    // Methods
    static Person();
    public Person();
    public void CopyDataFromSurrogate(PersonSurrogate surrogate);

    // Nested Types
    [DataContract(Name="Person", Namespace="myNamespace")]
    private class PersonSurrogate
    {
        // Fields
        [DataMember(EmitDefaultValue=false)]
        public string Firstname;
        [DataMember(EmitDefaultValue=false)]
        public string Lastname;
        [DataMember(EmitDefaultValue=false, Order=0x63)]
        public string OriginalValue_Firstname;
        [DataMember(EmitDefaultValue=false, Order=0x63)]
        public string OriginalValue_Lastname;
        [DataMember(EmitDefaultValue=false, Order=0x63)]
        public int OriginalValue_PersonID;
        [DataMember(EmitDefaultValue=false)]
        public int PersonID;
        [DataMember]
        public int SerializationID;

        // Methods
        public PersonSurrogate();
        public PersonSurrogate(Person source, List<object> graphList);
    }


All this is done invisible to the developer.
Now the datacontract serializer is able to serialize using the surrogates. The xml looks like:
<Person xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="myNamespace">
       <FieldedNumbers xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">  
           <d2p1:int>12</d2p1:int>  
           <d2p1:int>24</d2p1:int>  
       </FieldedNumbers>  
       <FirstName>Ruurd</FirstName>  
       <LastName>Boeke</LastName>  
       <Numbers xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">  
          <d2p1:int>31</d2p1:int> 
           <d2p1:int>29</d2p1:int> 
       </Numbers> 
       <Orders> 
           <Order> 
               <Amount>12</Amount> 
               <Customer> 
                   <SerializationID>0</SerializationID> 
               </Customer> 
               <OrderID>12</OrderID> 
               <ProductID>19</ProductID> 
               <SerializationID>1</SerializationID> 
           </Order> 
           <Order> 
               <Amount>12</Amount> 
               <Customer> 
                   <SerializationID>0</SerializationID> 
               </Customer> 
               <OrderID>13</OrderID> 
               <ProductID>255</ProductID> 
               <SerializationID>2</SerializationID> 
           </Order> 
       </Orders> 
       <PersonID>23</PersonID> 
       <SerializationID>0</SerializationID> 
       <Something>666</Something>
  </Person>


A datacontractsurrogate is supplied and should be attached to the service with an attribute
    [CircularReferenceSurrogateAttribute]
    [ServiceContract(Namespace = "Http://sitechno.Employee")]
    [ServiceKnownType("GetKnownTypes", typeof(KnownTypeHelper))]
    public interface IEmployeeService
    {
        [OperationContract]
        List<Employee> GetEmployees(string lastNameBeginsWith);

        [OperationContract]
        Person SavePerson(Person person);

        [OperationContract]
        Employee GetEmployee(int id);
    }
 


This will trigger the datacontractserializer to use the surrogate while serializing and call the 'CopyDataFromSurrogate' method when deserializing.

Original Values

The surrogate does not only have properties that match the parentType, but also creates properties that can be used to hold 'original values'.
The surrogate will try to fetch these values according to the supplied interface:
[CreateSerializeSurrogate("EditableBusinessObjects.Postsharp.Public.IEditableBusinessObject, EditableBusinessObjects.Postsharp.Public")]
Here, we are using the EditableBusinessObject interface. The surrogate will cast the parentType to this interface and call the 'GetOriginalValue' on it. This value will be placed in the originalvalue property that was created.

Apart from Editable Business Objects, the PostSharp4EF can also be used as a supplier of original values:
[CreateSerializeSurrogate("EntityFrameworkContrib.PostSharp4EF.ISerializationHelp, EntityFrameworkContrib.PostSharp4EF")]

The editable business objects keeps the original values in a dictionary. The PostSharp4EF (Poco) enhancements actually creates properties on the parenttype and fills them when signalled to.

Last edited Apr 16, 2008 at 8:10 PM by RBoeke, version 1

Comments

No comments yet.