none
EF POCO object graph with circular references deserialization not working properly RRS feed

  • Question

  • Hi community, I experience some strange behavior when receiving an object graph from a WCF service call. The graph represents a collection of POCO entities which have children, which in return have reference back to their parents. The POCO definitions are generated using the EF4 POCO template with WCF support, e.g. all entities are properly decorated with DataMember(IsReference=true) as well as KnowType(typeof(entity)) attributes. I use however EF5. EF proxy generation and lazy loading are disabled.

    Upon debugging I have seen that on the server side the graph is returned correctly by the LINQ query. On the client side however some of the child elements of the parents have null value, yet properties on the parent which holds the Ids of the children are set with correct values.

    Upon inspecting the WCF response message received on the client side it seems that the XML looks correct when it comes to the references of the objects. Which leaves me with the suspicion that the deserialization on the client side is not working properly.

    I have generated the proxies manually with svcutil by pointing to the assemblies which contain the entity types and the type of collections to be used by the client proxies (List<> instead of arrays and one custom collection that is a wrapper of a List<>).

    I have read similar threads on the forums which advise for removing IsReference=true on the corresponding children types and marking the required data members with IsRequired=true. This will however result in a much bigger messages over the wire. And as I said already, the XML seams correct, so the serialization process is producing the proper result. If there is an issue with the deserialization then it should be fixed instead of going for not that efficient solutions.

    Few more words about my set-up: WCF 4.0, ws2007HttpBinding with default settings, .NET 4.0 for both the web site(client) and the web services, MVC 3

    Thanks in advance!


    • Edited by SvetoslavVasilev Thursday, August 8, 2013 9:29 PM Forgot some details
    Thursday, August 8, 2013 4:25 PM

Answers

  • Hi Tony, thanks for your interest in the issue. I have spent some more time investigating it and can confirm that I have found the real cause. Luckily the deserializer is not the guilty one, though its behavior might be altered a bit to address such scenarios.

    The cause for this issue lied in the setter for the entity properties which represent a foreign key to a referenced entity. In my particular case I had a parent entity - Company, which had 2 simple type properties (SolicitorId and AccountantId) representing the foreign keys to each of the two child entities,  respectively Solicitor and Accountant. The model is such that a Solicitor and Accountant can have more than one companies they represent/work for. Let me remind you as well that I am referencing the assembly with the entity definitions rather than having them generated as part of the client proxies.

    The code in the setter for the SolicitorId property in the Company entity, generated by the T4 template looked originally like this:

    set
    {
        if (_solicitorId != value)
        {
            if (Solicitor != null && Solicitor.Id != value)
            {
                 Solicitor= null;
            }
            _solicitorId = value;
         }
    }
      

    This actively set any instance of Solicitor, held by a Company, to null in case there was a mismatch of the value being currently set and the actual value of the Id property of the Solicitor. While this sounds rather logical, it proofs to be wrong strategy when set in the context of deserializing the entity graph from within a WCF call response.

    It appears that the DataContractSerializer creates an instance of an entity the moment it hits an element in the XML representing that entity. And when creating those instances all the property values held by that entity are set to their defaults. Then whenever an element that corresponds to a certain entity property is read from the XML, its value is first set then. This is the behavior that caused this issue given the way the entities were serialized down the wire. The XML looked like this:

    <a:Collection xmlns:b="http://schemas.datacontract.org/2004/07/SomeNameSpce"> <b:Company z:Id="i1" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/"> ... <b:Accountant z:Id="i2"> ... <b:Companies> <b:Company z:Ref="i1"></b:Company> <b:Company z:Id="i3"> <b:Accountant z:Ref="i2"></b:Accountant> <b:AccountantId>1</b:AccountantId> <b:Solicitor z:Id="i4"> <b:Companies> <b:Company z:Ref="i1"></b:Company> <b:Company z:Id="i5"> <b:Accountant z:Id="i6"> ....

    <b:Companies> <b:Company z:Ref="i5"></b:Company> </b:Companies> </b:Accountant> <b:AccountantId>83</b:AccountantId> </b:Company> <b:Company z:Id="i7"> ... <b:Accountant z:Id="i8"> <b:City>Oslo</b:City> <b:Companies> <b:Company z:Ref="i7"></b:Company> <b:Company z:Id="i9"> ... <b:Accountant z:Ref="i8"></b:Accountant> <b:AccountantId>5</b:AccountantId> ... <b:Solicitor z:Ref="i4"></b:Solicitor> <b:SolicitorId>2</b:SolicitorId> </b:Company> <b:Company z:Id="i10"> ... <b:Accountant z:Ref="i8"></b:Accountant> <b:AccountantId>5</b:AccountantId> ... <b:Solicitor z:Id="i11"> ... <b:Companies> <b:Company z:Ref="i10"></b:Company> </b:Companies> ...

    <b:SolicitorId>6</b:SolicitorId> </b:Solicitor> </b:Company> ........... </b:Company> <b:Company z:Ref="i3"></b:Company> <b:Company z:Ref="i9"></b:Company> <b:Company z:Ref="i12"></b:Company> ... </b:Solicitor> </b:Company> ..... </b:Companies> .....

    <b:AccountantId>22</b:AccountantId>

    </b:Accountant> .... </a:Collection>

    What should be noted in the above snippet is that the Id of the referenced entity, f.ex. Accountant comes after the collection of Companies that are referenced by this accountant. Considering the behavior of the deserializer described above this will effectively set the Accountant instance contained in its parent Company entity to null when we come to the point where the AccountId value is positioned in the XML (in this case after the Companies collection). Again this is because an instance of the Accountant entity has been already created and its Id property has a value of 0.

    Obviously a logical way to solve this is to force the order of the entity properties in the XML stream by setting the Order property of the DataMember attribute. However, when working with the POCO template this seems kind a difficult. There is no place in the EDMX model, or at least known to me, where one can define the order of the properties that should be later used by the template upon entity generation. And to leave this as a manual step after entity generation is doomed to fail.

    What I ended up with was to modify the setter of the foreign key properties to make one extra check - if the PK of the referenced property is not set to a default value:

    if (_solicitorId != value)
    {
       if (Solicitor!= null && Solicitor.Id != value
       	&& Solicitor.Id != default(int))
       {
           Solicitor = null;
       }
       _solicitorId = value;
    }
    This check effectively covers the case i described above and thus the object graph is finally deserialized as expected from the response XML.

    Thanks, Svetoslav



    Monday, August 12, 2013 12:31 PM

All replies

  • No one out there who have experienced similar issues? Not a single soul? Probably I have the strangest entity relationships in my EF model or the WCF setup is completely gone fishing...
    While I am waiting for an answer I wrote a unit test which reproduces the problem. I took the response message from the WCF trace logs, put it in a file and use this one to feed the DataContractSerializer with it. The result is E.X.A.C.T.L.Y the same as from the WCF service call - the exact same child entities of the exact same parents are set to null in the result of the test as well. This proves that the behavior of the (de)serializer is consistent regardless of the execution environment.

    Friday, August 9, 2013 9:15 PM
  • hello cyberbyber,

    Would you be able to share the sample which reproduces the problem? I have a limited amount of time I can work on your issue and if you provide a sample, it will save much of the cost of making my own.

    Please let me know.

    Thanks,

    Tony

    Sunday, August 11, 2013 4:57 PM
  • Hi Tony, thanks for your interest in the issue. I have spent some more time investigating it and can confirm that I have found the real cause. Luckily the deserializer is not the guilty one, though its behavior might be altered a bit to address such scenarios.

    The cause for this issue lied in the setter for the entity properties which represent a foreign key to a referenced entity. In my particular case I had a parent entity - Company, which had 2 simple type properties (SolicitorId and AccountantId) representing the foreign keys to each of the two child entities,  respectively Solicitor and Accountant. The model is such that a Solicitor and Accountant can have more than one companies they represent/work for. Let me remind you as well that I am referencing the assembly with the entity definitions rather than having them generated as part of the client proxies.

    The code in the setter for the SolicitorId property in the Company entity, generated by the T4 template looked originally like this:

    set
    {
        if (_solicitorId != value)
        {
            if (Solicitor != null && Solicitor.Id != value)
            {
                 Solicitor= null;
            }
            _solicitorId = value;
         }
    }
      

    This actively set any instance of Solicitor, held by a Company, to null in case there was a mismatch of the value being currently set and the actual value of the Id property of the Solicitor. While this sounds rather logical, it proofs to be wrong strategy when set in the context of deserializing the entity graph from within a WCF call response.

    It appears that the DataContractSerializer creates an instance of an entity the moment it hits an element in the XML representing that entity. And when creating those instances all the property values held by that entity are set to their defaults. Then whenever an element that corresponds to a certain entity property is read from the XML, its value is first set then. This is the behavior that caused this issue given the way the entities were serialized down the wire. The XML looked like this:

    <a:Collection xmlns:b="http://schemas.datacontract.org/2004/07/SomeNameSpce"> <b:Company z:Id="i1" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/"> ... <b:Accountant z:Id="i2"> ... <b:Companies> <b:Company z:Ref="i1"></b:Company> <b:Company z:Id="i3"> <b:Accountant z:Ref="i2"></b:Accountant> <b:AccountantId>1</b:AccountantId> <b:Solicitor z:Id="i4"> <b:Companies> <b:Company z:Ref="i1"></b:Company> <b:Company z:Id="i5"> <b:Accountant z:Id="i6"> ....

    <b:Companies> <b:Company z:Ref="i5"></b:Company> </b:Companies> </b:Accountant> <b:AccountantId>83</b:AccountantId> </b:Company> <b:Company z:Id="i7"> ... <b:Accountant z:Id="i8"> <b:City>Oslo</b:City> <b:Companies> <b:Company z:Ref="i7"></b:Company> <b:Company z:Id="i9"> ... <b:Accountant z:Ref="i8"></b:Accountant> <b:AccountantId>5</b:AccountantId> ... <b:Solicitor z:Ref="i4"></b:Solicitor> <b:SolicitorId>2</b:SolicitorId> </b:Company> <b:Company z:Id="i10"> ... <b:Accountant z:Ref="i8"></b:Accountant> <b:AccountantId>5</b:AccountantId> ... <b:Solicitor z:Id="i11"> ... <b:Companies> <b:Company z:Ref="i10"></b:Company> </b:Companies> ...

    <b:SolicitorId>6</b:SolicitorId> </b:Solicitor> </b:Company> ........... </b:Company> <b:Company z:Ref="i3"></b:Company> <b:Company z:Ref="i9"></b:Company> <b:Company z:Ref="i12"></b:Company> ... </b:Solicitor> </b:Company> ..... </b:Companies> .....

    <b:AccountantId>22</b:AccountantId>

    </b:Accountant> .... </a:Collection>

    What should be noted in the above snippet is that the Id of the referenced entity, f.ex. Accountant comes after the collection of Companies that are referenced by this accountant. Considering the behavior of the deserializer described above this will effectively set the Accountant instance contained in its parent Company entity to null when we come to the point where the AccountId value is positioned in the XML (in this case after the Companies collection). Again this is because an instance of the Accountant entity has been already created and its Id property has a value of 0.

    Obviously a logical way to solve this is to force the order of the entity properties in the XML stream by setting the Order property of the DataMember attribute. However, when working with the POCO template this seems kind a difficult. There is no place in the EDMX model, or at least known to me, where one can define the order of the properties that should be later used by the template upon entity generation. And to leave this as a manual step after entity generation is doomed to fail.

    What I ended up with was to modify the setter of the foreign key properties to make one extra check - if the PK of the referenced property is not set to a default value:

    if (_solicitorId != value)
    {
       if (Solicitor!= null && Solicitor.Id != value
       	&& Solicitor.Id != default(int))
       {
           Solicitor = null;
       }
       _solicitorId = value;
    }
    This check effectively covers the case i described above and thus the object graph is finally deserialized as expected from the response XML.

    Thanks, Svetoslav



    Monday, August 12, 2013 12:31 PM