Problem Deserializing an entity

Posts   
 
    
Posts: 98
Joined: 09-Feb-2005
# Posted on: 10-Jul-2006 01:56:25   

Adapter, llbl2.0 For auditing, I've created a table that stores the username, user object id, and the serialized object. For testing, I try to create an object, and then save it, which triggers the save, and also the audit. Things seem to be working fine until I deserialize the xml and try to compare the objects.

Here's what I've got: -- Serializing


            using (TransactionScope ts = new TransactionScope())
            {
                using (DataAccessAdapter adapter = new DataAccessAdapter())
                {
                    if (adapter.SaveEntity(customer, refetchAfterSave, true))
                    {
                        // Create Audit Item only after the save takes place so that auto-generated fields will be set appropriately,
                        // as well as any other new fields that are set and assigned to the customer, but which don't themselves
                        // have id's available yet.  If the save is successful, then all of this will be ready.
                        CustomerAuditEntity customerAudit = new CustomerAuditEntity();
                        customerAudit.UserAdname = VCSecurity.GetUserADName();
                        string whatChanged = string.Empty;
                        customer.WriteXml(XmlFormatAspect.Compact | XmlFormatAspect.DatesInXmlDataType | XmlFormatAspect.MLTextInCDataBlocks, out whatChanged);
                        customerAudit.WhatChanged = whatChanged;
                        customerAudit.Customer = customer;
                        try
                        {
                            /// The customer audit entity is not saved recursively, so the audit field itself will not be serialized 
                            /// into the audit field (but all previous audit fields will be).  This should allow multiple step undo.
                            adapter.SaveEntity(customerAudit, true, false);// true, true);
                            ts.Complete();
                            return customer;
                        }
                        catch (Exception ex)
                        {
                            throw new Exception("Unable to save audit information for this collateral.  Please send this message to the administrator", ex);
                        }
                    }
                    else
                        throw new Exception("Saving the customer failed.");
                }
            }

-- Deserializing


CustomerEntity auditCustomer = new CustomerEntity();
            try
            {
                auditCustomer.ReadXml(custAudit.WhatChanged);

            }
            catch (Exception ex)
            {
                Console.WriteLine(custAudit.WhatChanged);
                throw;
            }

-- Exception

sap.valuecollateral.tests.CustomerManagerTest.CustomerAudit : System.NullReferenceException : Object reference not set to an instance of an object.

-- Stack

at SD.LLBLGen.Pro.ORMSupportClasses.EntityBase2.Xml2Entity(XmlNode node, Dictionary2 processedObjectIDs, List1 nodeEntityReferences) at SD.LLBLGen.Pro.ORMSupportClasses.EntityCollectionBase21.Xml2EntityCollection(XmlNode node, Dictionary2 processedObjectIDs, List1 nodeEntityReferences) at SD.LLBLGen.Pro.ORMSupportClasses.EntityCollectionBase21.SD.LLBLGen.Pro. ORMSupportClasses.IEntityCollectionAccess2.Xml2EntityCollection(XmlNode node, Dictionary2 processedObjectIDs, List1 nodeEntityReferences) at SD.LLBLGen.Pro.ORMSupportClasses.EntityBase2.Xml2Entity(XmlNode node, Dictionary2 processedObjectIDs, List1 nodeEntityReferences) at SD.LLBLGen.Pro.ORMSupportClasses.EntityBase2.ReadXml(XmlNode node) at SD.LLBLGen.Pro.ORMSupportClasses.EntityBase2.ReadXml(String xmlData) at sap.valuecollateral.tests.CustomerManagerTest.CustomerAudit() in G:\My Documents\code\Work\sap\valuecollateral\website2005\trunk\tests\sap.valuecollateral.tests\CustomerManagerTest.cs:line 419

-- XML that is being deserialized. (sorry...)

PLEASE, this isn't going to help anyone. -- Otis (snip)

There's nothing in the inner exception, and the null exception is a little too vague to help me track things down. Any suggestion on where to start looking would be great.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 10-Jul-2006 10:30:02   

I've snipped away the xml, as it was a massive block and it doesn't help anything as it's layout specific, so it can't be used to test anything.

Do you deserialize in the same application? Or in a different application? (i.e .that the entity classes couldbe different)

Is it possible for you to run a debug build of the ORMSupportClasses to see at which line the crash happens?

Frans Bouma | Lead developer LLBLGen Pro
Posts: 98
Joined: 09-Feb-2005
# Posted on: 10-Jul-2006 15:17:30   

cry cry cry Oh I see how it is... My XML's not good enough for you huh? Heh heh. I was wondering if I was gonna get any grief for that.

Ok, we're in the same application, so the entity classes are the same.

I ran a debug version and here is the new exception/stack

sap.valuecollateral.tests.CustomerManagerTest.CustomerAudit : System.NullReferenceException : Object reference not set to an instance of an object.

at SD.LLBLGen.Pro.ORMSupportClasses.EntityBase2.Xml2Entity(XmlNode node, Dictionary2 processedObjectIDs, List1 nodeEntityReferences) in C:\Program Files\Solutions Design\LLBLGen Pro v2.0\RuntimeLibraries\Sourcecode\Net2.x\ORMSupportClasses\EntityBase2.cs:line 722 at SD.LLBLGen.Pro.ORMSupportClasses.EntityCollectionBase21.Xml2EntityCollection(XmlNode node, Dictionary2 processedObjectIDs, List1 nodeEntityReferences) in C:\Program Files\Solutions Design\LLBLGen Pro v2.0\RuntimeLibraries\Sourcecode\Net2.x\ORMSupportClasses\EntityCollectionBase2.cs:line 656 at SD.LLBLGen.Pro.ORMSupportClasses.EntityCollectionBase21.SD.LLBLGen.Pro.ORMSupportClasses. IEntityCollectionAccess2.Xml2EntityCollection(XmlNode node, Dictionary2 processedObjectIDs, List1 nodeEntityReferences) in C:\Program Files\Solutions Design\LLBLGen Pro v2.0\RuntimeLibraries\Sourcecode\Net2.x\ORMSupportClasses\EntityCollectionBase2.cs:line 829 at SD.LLBLGen.Pro.ORMSupportClasses.EntityBase2.Xml2Entity(XmlNode node, Dictionary2 processedObjectIDs, List1 nodeEntityReferences) in C:\Program Files\Solutions Design\LLBLGen Pro v2.0\RuntimeLibraries\Sourcecode\Net2.x\ORMSupportClasses\EntityBase2.cs:line 723 at SD.LLBLGen.Pro.ORMSupportClasses.EntityBase2.ReadXml(XmlNode node) in C:\Program Files\Solutions Design\LLBLGen Pro v2.0\RuntimeLibraries\Sourcecode\Net2.x\ORMSupportClasses\EntityBase2.cs:line 667 at SD.LLBLGen.Pro.ORMSupportClasses.EntityBase2.ReadXml(String xmlData) in C:\Program Files\Solutions Design\LLBLGen Pro v2.0\RuntimeLibraries\Sourcecode\Net2.x\ORMSupportClasses\EntityBase2.cs:line 653 at sap.valuecollateral.tests.CustomerManagerTest.CustomerAudit() in G:\My Documents\code\Work\sap\valuecollateral\website2005\trunk\tests\sap.valuecollateral.tests\CustomerManagerTest.cs:line 419

PS. It took everything I had to not copy and paste the xml back into this message. wink

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 10-Jul-2006 16:29:44   

Heh, well you've go back to your XML now as this is the line where it breaks:


case "EntityCollectionReference":
    // create a new instance.
    XmlNode entityCollectionNode = currentElement.FirstChild;
    // get a reference to the collection object.
    IEntityCollection2 referencedEntityCollection = (IEntityCollection2)properties[currentElement.Attributes["PropertyName"].Value].GetValue(this); // line 722

This can happen in the following situation: - you created an entity, say Customer, and it has a field mapped onto an 1:n relation (or m:n), for example called Orders - you create an instance of Customer(Entity) and serialize it to XML and store that XML somewhere. - later on you change the field mapped onto customer - Order into OrderCollection. - you then try to deserialize the xml you saved earlier.

The XML code in llblgen pro assumes the same versions of the code are used for serialization and deserialization.

Is this the case in your situation?

Frans Bouma | Lead developer LLBLGen Pro
Posts: 98
Joined: 09-Feb-2005
# Posted on: 10-Jul-2006 19:56:57   

Nothing so simple I'm afraid. The schema hasn't changed. Both the serialization and deserialization take place within a unit test. Here's the overview of what is happening. 1. I have a helper class that creates a large object graph via reflection of my LLBL entities.
2. Start a transaction 3. I save a target entity (in this case Customer) recursively. The save succeeds.
4. I then create an audit field that contains all of what I just saved in an ntext field. The audit field is also an llbl entity, and the field (WhatChanged) accepts the compact xml written out by the just saved customer (which was saved with reload=true, recursive=true). 5. The save works. 6. I then create a new CustomerEnitty in my test, and run ReadXML from this WhatChanged field. This is where I'm getting the error.

I've determined which snippet of XML is causing the problem:

<EntityCollectionReference PropertyName="CollateralIndustrySubSegmentCollection"> <CollateralIndustrySubSegmentCollection> <Entities> <CollateralIndustrySubSegmentEntity AssetId="222" IndustrySubSegmentId="359"> <ObjectID>7330e40a-67e1-4672-b3e9-6977bcac5a3d</ObjectID> <EntityReference PropertyName="IndustrySubSegment"> <LookupIndustryEntity IndustryId="359"> <EntityCollectionReference PropertyName="ChildIndustryCollection"> <ChildIndustryCollection> <Entities/> <AllowRemove>True</AllowRemove> <AllowEdit>True</AllowEdit> <EntityFactoryToUse Assembly="sap.valuecollateral.dl, Version=1.0.2382.14734, Culture=neutral, PublicKeyToken=null" Type="sap.valuecollateral.dl.FactoryClasses.LookupIndustryEntityFactory"/> <AllowNew>True</AllowNew> </ChildIndustryCollection> </EntityCollectionReference> <EntityReference PropertyName="ParentIndustry"/> <IsNew>False</IsNew> <ObjectID>ba416f11-ee69-4723-82d3-5171a51fe880</ObjectID> <Fields> <IndustryId> <CurrentValue>359</CurrentValue> <DbValue>359</DbValue> <IsChanged>False</IsChanged> <IsNull>False</IsNull> </IndustryId> <ParentIndustryId> <CurrentValue Type="Unknown"/> <DbValue Type="Unknown"/> <IsChanged>False</IsChanged> <IsNull>True</IsNull> </ParentIndustryId> <Vcid> <CurrentValue>100</CurrentValue> <DbValue>100</DbValue> <IsChanged>False</IsChanged> <IsNull>False</IsNull> </Vcid> <IndustryTypeId> <CurrentValue>1</CurrentValue> <DbValue>1</DbValue> <IsChanged>False</IsChanged> <IsNull>False</IsNull> </IndustryTypeId> <Name> <CurrentValue>Name 0</CurrentValue> <DbValue>Name 0</DbValue> <IsChanged>False</IsChanged> <IsNull>False</IsNull> </Name> <Description> <CurrentValue>Description 0</CurrentValue> <DbValue>Description 0</DbValue> <IsChanged>False</IsChanged> <IsNull>False</IsNull> </Description> </Fields> <IsDirty>False</IsDirty> <EntityState>Fetched</EntityState> <SavedFieldSets/> </LookupIndustryEntity> </EntityReference> <EntityReference PropertyName="Collateral"> <ProcessedObjectReference ObjectID="6c30335b-d4df-448d-9a61-cbc3a527528d"/> </EntityReference> <IsNew>False</IsNew> <Fields> <AssetId> <CurrentValue>222</CurrentValue> <DbValue>222</DbValue> <IsChanged>False</IsChanged> <IsNull>False</IsNull> </AssetId> <IndustrySubSegmentId> <CurrentValue>359</CurrentValue> <DbValue>359</DbValue> <IsChanged>False</IsChanged> <IsNull>False</IsNull> </IndustrySubSegmentId> </Fields> <IsDirty>False</IsDirty> <EntityState>Fetched</EntityState> <SavedFieldSets/> </CollateralIndustrySubSegmentEntity> </Entities> <AllowRemove>True</AllowRemove> <AllowEdit>True</AllowEdit> <EntityFactoryToUse Assembly="sap.valuecollateral.dl, Version=1.0.2382.14734, Culture=neutral, PublicKeyToken=null" Type="sap.valuecollateral.dl.FactoryClasses.CollateralIndustrySubSegmentEntityFactory"/> <AllowNew>True</AllowNew> </CollateralIndustrySubSegmentCollection> </EntityCollectionReference>

Looks like this may have something to do w/ the TargetPerEntityHierarchy thing I mentioned in my last question. To recap, I have:

LookupIndustry (supertype) LookupIndustryScenario (subtype of Industry, only added field is a bit field) LookupIndustrySubSegment (subtype of Industry, no added fields)

I have a Collateral object that contains collections of these 2 entities. Collateral.IndustryScenarioCollection (M:N) Collateral.IndustrySubSegmentCollection (M:N)

The Join collections are: Collateral.CollateralIndustryScenarioCollection Collateral.CollateralIndustrySubSegmentCollection

Due to the way TargetPerEntityHierarchy works, the collections are really just of the SuperType LookupIndustry. Activating the trace during the save, I can see that I do have an Industry being saved, and that it has an @@IDENTITY value returned for it.

The related entities that are also saved reference this ID value. However, immediately after the save, the database doesn't actually contain the LookupIndustry record. It contains everything else I was trying to save though. Strange.

Any other thoughts? Thanks for your help.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 11-Jul-2006 11:40:09   

jeffdeville wrote:

Looks like this may have something to do w/ the TargetPerEntityHierarchy thing I mentioned in my last question. To recap, I have:

Could you please place a breakpoint at line 722 in EntityBase2 after you've loaded the file into your project and run the unittest with the debugger? this way you'll break at that line and you can see what's null and causing the problem.

LookupIndustry (supertype) LookupIndustryScenario (subtype of Industry, only added field is a bit field) LookupIndustrySubSegment (subtype of Industry, no added fields)

I have a Collateral object that contains collections of these 2 entities. Collateral.IndustryScenarioCollection (M:N) Collateral.IndustrySubSegmentCollection (M:N)

The Join collections are: Collateral.CollateralIndustryScenarioCollection Collateral.CollateralIndustrySubSegmentCollection

Due to the way TargetPerEntityHierarchy works, the collections are really just of the SuperType LookupIndustry.

correct.

Activating the trace during the save, I can see that I do have an Industry being saved, and that it has an @@IDENTITY value returned for it.

The related entities that are also saved reference this ID value. However, immediately after the save, the database doesn't actually contain the LookupIndustry record. It contains everything else I was trying to save though. Strange. Any other thoughts? Thanks for your help.

That's really strange. If a transaction commits and the save was successful you should see the data in the db. However I don't see any dirty flags being true in your xml snippet.

The thing is: An entity instance is reading its own xml. So it creates the property set of itself, then uses the XML child nodes of its own XML node to see which data to read into itself. The line where it goes wrong suggests that there is an entitycollection reference in the XML (e.g. 'CollateralIndustrySubSegmentCollection') however the entity doesn't contain that property (nor a supertype) and therefore the property returns null, and getvalue call then crashes. Which is really strange.

Frans Bouma | Lead developer LLBLGen Pro
Posts: 98
Joined: 09-Feb-2005
# Posted on: 11-Jul-2006 14:53:46   

Ok, here's what's going on. I have a customer entity that has a collection of assets. AssetEntity is the supertype of a Target Per Entity hierarchy. Currently, there is one subtype, CollateralEntity.

The code that is saved creates a customer, and in it's AssetCollection, stores a CollateralEntity. When this is persisted, the collateral writes out his xml with the additional properties available to the CollateralEntity. When deserialized, it chokes because it doesn't have any knowledge of any of the CollateralEntity fields. I'm assuming that it is an AssetEntity that has been instantiated to try to deserialize a Collateral.

Any suggestions on how to have the deserialization process inspect the xml to determine what type to deserialize before deserializing it?

If not, it looks like the work around would be to create a seperate collection for each AssetEntity subtype on the customer object. Not ideal, but workable.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 11-Jul-2006 19:10:00   

With a hierarchy of several entities I'm able to reproduce this. I'm a little stunned as it should determine the types without a problem and work from there. I'll see if compact/non-compact has any influence on this as well. I get the crash on a slightly different spot, but in code which uses the same technique: grab a property and read the value.

edit: ok, NON-compact XML works. So if you switch to NON-compact XML your code should work. it will produce more xml though.

With compact XML, there's no type of the entity specified, and it then uses the factory set for the collection, as you suggested correctly that's then not going to produce a proper entity, as that will produce an instance of the entity related to the factory, not related to the actual data.

However, there is maybe a small chance this can work, if I can instantiate the type of the entity using the tag of the entity as the typename. However this also might fail in some situations... rock, hard place...

(edit)... bleh... the tagname isn't sufficient, it can't find the type to instantiate... Hmm. So in compact mode, it can't find the type to instantiate, as that's not available... in non-compact mode it is available.

Frans Bouma | Lead developer LLBLGen Pro
Posts: 98
Joined: 09-Feb-2005
# Posted on: 11-Jul-2006 19:36:50   

Great, well that will get me going. If Compact works in the future, I'll switch back to that. Thanks!

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 11-Jul-2006 20:50:50   

I have no idea how to get the types in compact mode... unless I store the type info in the entity tags which is not what you want in compact mode...

Frans Bouma | Lead developer LLBLGen Pro