EntityCollection WCF Deserialization

Posts   
 
    
saggett
User
Posts: 50
Joined: 12-Nov-2007
# Posted on: 15-Nov-2007 16:20:49   

I'm passing an Entity Collection as a parameter in this Service Contract method and I'm encountering an ArgumentNullException, "Value cannot be null.\r\nParameter name: g".

I'm calling my WCF Service via this method:

[OperationContract] void SaveEntityCollection(IEntityCollection2 collection, IEntityCollection2 deletedEntities, bool recurse);

In this case, collection is null and deletedEntities is of type EntityCollection and contains three AddOnCostEntity entities. These entities do not possess any child collections or custom properties.

I'm running LLBLGen v2.5 Final (October 25th, 2007) and my runtime library version is 2.5.07.1019.

Here's the stack trace:

at System.Guid..ctor(String g) at SD.LLBLGen.Pro.ORMSupportClasses.EntityBase2.Xml2Entity(XmlReader reader, Dictionary2 processedObjectIDs, List1 nodeEntityReferences) at SD.LLBLGen.Pro.ORMSupportClasses.EntityBase2.Xml2Entity(XmlReader reader, Dictionary2 processedObjectIDs, List1 nodeEntityReferences) at SD.LLBLGen.Pro.ORMSupportClasses.EntityCollectionBase21.Xml2EntityCollection(XmlReader reader, Dictionary2 processedObjectIDs, List1 nodeEntityReferences) at SD.LLBLGen.Pro.ORMSupportClasses.EntityCollectionBase21.ReadXml(XmlReader reader, XmlFormatAspect format) at SD.LLBLGen.Pro.ORMSupportClasses.EntityCollectionBase2`1.System.Xml.Serialization.IXmlSerializable.ReadXml(XmlReader reader) at System.Runtime.Serialization.XmlObjectSerializerReadContext.ReadIXmlSerializable(XmlSerializableReader xmlSerializableReader, XmlReaderDelegator xmlReader, XmlDataContract xmlDataContract, Boolean isMemberType) at System.Runtime.Serialization.XmlDataContract.ReadXmlValue(XmlReaderDelegator xmlReader, XmlObjectSerializerReadContext context) at System.Runtime.Serialization.XmlObjectSerializerReadContext.InternalDeserialize(XmlReaderDelegator reader, String name, String ns, DataContract& dataContract) at System.Runtime.Serialization.XmlObjectSerializerReadContext.InternalDeserialize(XmlReaderDelegator xmlReader, Type declaredType, DataContract dataContract, String name, String ns) at System.Runtime.Serialization.DataContractSerializer.InternalReadObject(XmlReaderDelegator xmlReader, Boolean verifyObjectName) at System.Runtime.Serialization.XmlObjectSerializer.ReadObjectHandleExceptions(XmlReaderDelegator reader, Boolean verifyObjectName) at System.Runtime.Serialization.DataContractSerializer.ReadObject(XmlDictionaryReader reader, Boolean verifyObjectName) at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.DeserializeParameterPart(XmlDictionaryReader reader, PartInfo part, Boolean isRequest) at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.DeserializeParameter(XmlDictionaryReader reader, PartInfo part, Boolean isRequest) at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.DeserializeParameters(XmlDictionaryReader reader, PartInfo[] parts, Object[] parameters, Boolean isRequest) at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.DeserializeBody(XmlDictionaryReader reader, MessageVersion version, String action, MessageDescription messageDescription, Object[] parameters, Boolean isRequest) at System.ServiceModel.Dispatcher.OperationFormatter.DeserializeBodyContents(Message message, Object[] parameters, Boolean isRequest) at System.ServiceModel.Dispatcher.OperationFormatter.DeserializeRequest(Message message, Object[] parameters) at System.ServiceModel.Dispatcher.DispatchOperationRuntime.DeserializeInputs(MessageRpc& rpc) at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc) at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc) at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage4(MessageRpc& rpc) at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage3(MessageRpc& rpc) at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage2(MessageRpc& rpc) at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage1(MessageRpc& rpc) at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)

and here's the trace log:

Method Enter: EntityCollectionBase2.EntityCollection2Xml Method Enter: EntityBase2.Entity2Xml Method Exit: EntityBase2.Entity2Xml Method Enter: EntityBase2.Entity2Xml Method Exit: EntityBase2.Entity2Xml Method Enter: EntityBase2.Entity2Xml Method Exit: EntityBase2.Entity2Xml Method Enter: EntityBase2.Entity2Xml Method Exit: EntityBase2.Entity2Xml Method Enter: EntityBase2.Entity2Xml Method Exit: EntityBase2.Entity2Xml Method Exit: EntityCollectionBase2.EntityCollection2Xml Method Enter: EntityCollectionBase2.Xml2EntityCollection(XmlReader..) Method Exit: EntityCollectionBase2.Xml2EntityCollection(XmlReader... Method Enter: EntityCollectionBase2.Xml2EntityCollection(XmlReader..) Method Enter: EntityBase2.Xml2Entity(XmlReader... Method Enter: EntityBase2.Xml2Entity(XmlReader... Method Exit: EntityBase2.Xml2Entity(XmlReader... Method Exit: EntityBase2.Xml2Entity(XmlReader... Method Exit: EntityCollectionBase2.Xml2EntityCollection(XmlReader...

Hope you can help.

Thanks, Stephen

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 15-Nov-2007 17:33:49   

Could you please run with the Debug build of the ormsupport classes dll (is in the runtime libs folder) by also placing the .pdb file in the app folder (on the deserialization side) so we can see the line numbers? simple_smile

Frans Bouma | Lead developer LLBLGen Pro
saggett
User
Posts: 50
Joined: 12-Nov-2007
# Posted on: 16-Nov-2007 11:58:03   

Did that, I'm now stepping through the lines of the ORMSupport code which is very handy indeed. simple_smile

Here's the stack trace I now receive with the exception (no idea why this one doesn't give the complete stack, we've just got the very top level):

at System.Guid..ctor(String g) at SD.LLBLGen.Pro.ORMSupportClasses.EntityBase2.Xml2Entity(XmlReader reader, Dictionary2 processedObjectIDs, List1 nodeEntityReferences) in c:\Myprojects\VS.NET Projects\LLBLGen Pro v2.0\RuntimeLibraries 2.5 .NET 2.x\ORMSupportClasses\EntityBase2.cs:line 1085

I was able to check the fields used in the Xml2Entity method, and found out that startElementName equals 'AddOnCostType' when the error occurs, which is a child entity of AddOnCostEntity via a m:1 relation, and the XmlReader.Value is empty. In this case, AddOnCost.AddOnCostType equals null on each of the three AddOnCost Entities being serialized / deserialized.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 16-Nov-2007 17:28:55   

Good info. Are the entities in the 'deletedEntities' entities which were tracked by a tracker collection ? If so, they're marked for deletion which will make serialization code chop off the hierarchy frm these entities. This means that related entities aren't serialized with them, as they only are deleted, not the complete graph.

It could be there's a bug in that code somewhere. Could you confirm that the entities in deletedEntities are from a tracker collection (i.e. 'deletedEntities' was the collection assigned to RemovedEntitiesTracker)

If you manually grabbed these entities and stored them in a collection, we've to setup a different test.

Frans Bouma | Lead developer LLBLGen Pro
saggett
User
Posts: 50
Joined: 12-Nov-2007
# Posted on: 16-Nov-2007 18:29:18   

The entities in deletedEntities are from a tracker collection, and so they're all marked for deletion, but the collection they're passed in isn't the collection.RemovedEntitiesTracker collection. This is the line of code used to make the service call:

endPoint.SaveEntityCollection(CreateNonGenericCollection(collection.DirtyEntities), CreateNonGenericCollection(collection.RemovedEntitiesTracker), recurse);

And this is the CreateNonGenericCollection method:

    private EntityCollection CreateNonGenericCollection(IEntityCollection2 genericCollection)
    {
        EntityCollection nonGenericCollection = new EntityCollection(genericCollection.EntityFactoryToUse);
        foreach (IEntity2 entity in genericCollection)
        {
            nonGenericCollection.Add((EntityBase2)entity);
        }
        return nonGenericCollection;
    }

I'm adding the entities contained in collection.RemovedEntitiesTracker to a new collection of type EntityCollection so that we always pass a non-generic collection rather than a generic collection.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 17-Nov-2007 10:58:12   

Ok, thanks for the info, that indeed narrows the focus where to look. The related entities in which hte crash occurs shouldn't be serialized at all, so there's a bug somewhere and I can now check the code to see if I made a mistake in implementing the algorithm simple_smile

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 19-Nov-2007 11:01:23   

Reproduced:


/// <summary>
/// Test which checks if the entities which are tracked using a delete tracker,aren't serializing over XML their related entities.
/// </summary>
[Test]
public void XMLSerializationDeserializationOfDeleteTrackedEntitiesTest()
{
    EntityCollection<OrderEntity> orders = new EntityCollection<OrderEntity>();
    using(DataAccessAdapter adapter = new DataAccessAdapter())
    {
        PrefetchPath2 path = new PrefetchPath2(EntityType.OrderEntity);
        path.Add(OrderEntity.PrefetchPathOrderDetails);
        adapter.FetchEntityCollection(orders, new RelationPredicateBucket(OrderFields.CustomerId == "CHOPS"), path);
    }

    Assert.AreEqual(8, orders.Count);

    EntityCollection<OrderEntity> tracker = new EntityCollection<OrderEntity>();
    orders.RemovedEntitiesTracker = tracker;

    // delete the first 2 orders
    orders.RemoveAt(0);
    orders.RemoveAt(0);

    Assert.AreEqual(2, tracker.Count);

    // now serialize to XML the entities in the tracker.
    string xml = string.Empty;
    tracker.WriteXml(XmlFormatAspect.Compact25 | XmlFormatAspect.DatesInXmlDataType | XmlFormatAspect.MLTextInCDataBlocks, out xml);

    // deserialize the orders back into new entities
    EntityCollection<OrderEntity> deserializedOrders = new EntityCollection<OrderEntity>();
    deserializedOrders.ReadXml(xml);

    Assert.AreEqual(0, deserializedOrders[0].OrderDetails.Count);
    Assert.AreEqual(0, deserializedOrders[1].OrderDetails.Count);
}

Looking into it.

Frans Bouma | Lead developer LLBLGen Pro
saggett
User
Posts: 50
Joined: 12-Nov-2007
# Posted on: 19-Nov-2007 11:10:08   

That's good, I'm glad that the problem wasn't due to a problem in my code and that I've helped you find a bug.

Also, I was wondering - what does LLBLGen stand for?

saggett
User
Posts: 50
Joined: 12-Nov-2007
# Posted on: 19-Nov-2007 11:15:31   

Okay, just looked it up myself (Lower Level Business Layer Gen). simple_smile

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 19-Nov-2007 11:27:52   

saggett wrote:

That's good, I'm glad that the problem wasn't due to a problem in my code and that I've helped you find a bug.

Fixed in next build (11192007, released later today).

The issue was that Compact25 format expects that if an element is specified, it has a value, otherwise the element shouldn't be there, to keep the XML as small as possible. The References to m:1 and 1:1 related entities were still there as element (no data), e.g. <Customer/>

This triggered the XML deserializer to instantiate a new entity and deserialize the element data into that, which was empty and thus it crashed.

I'll attach a build to this post so you can continue with your work, after I've ran all the unittests again. Attached.

Frans Bouma | Lead developer LLBLGen Pro