- Home
- LLBLGen Pro
- LLBLGen Pro Runtime Framework
TargetPerEntityHierarchy EntityCollection serialization
Joined: 07-Mar-2007
Hi
I have been searching high and low on this forum for an answer to this, but I never did find an answer that could help me out on this issue.
I am currently investigating possibilities and limitations with using inheritance in the data model for a project I am working on, and I stumpled upon this one.
(I am using SD.LLBLGen.Pro.ORMSupportClasses.NET20.dll version 2.0.07.0219)
I have a TargetPerEntityHierarchy scenario (very simple) with a table Contact.
Currently the Contact table has the fields ContactID, Discriminator, Person_Name, Location_Name ( I know, not a situation that calls for TargetPerEntityHierarchy but just for testing)
ContactEntity is super type for PersonEntity and LocationEntity.
ContactEntity has a self referencing m:n relation (I have read the various articles in here on that subject and I understand the implications with this, but it does not seem to influence this specific example as the example works perfectly if I remove the TargetPerEntityHierarchy)
This is what I am trying to do. I want to retreive a PersonEntity with all his relatives (ContactRelation) and send them through WCF (netTcpBinding), but the client throws an NullReferenceException:
IRelationPredicateBucket filter = new RelationPredicateBucket(ContactFields.ContactId == id);
// Make PersonEntity our starting point
IPrefetchPath2 path = new PrefetchPath2((int)EntityType.PersonEntity);
// Load all related contacts - 1 in this test case (Make sure that we use the right factory (PersonEntityFactory)
path.Add(PersonEntity.PrefetchPathContactRelation).SubPath.Add(ContactRelationEntity.PrefetchPathRelatedContact, new PersonEntityFactory());
PersonEntity entity;
using (DataAccessAdapter adapter = new DataAccessAdapter())
{
// Make sure to use the PersonEntityFactory so serialization works for the entity (subentity)
entity = adapter.FetchNewEntity(new PersonEntityFactory(), filter, path) as PersonEntity;
}
return entity;
Now the problem is that when the client receives the data, it tries to deserialize it as a ContactEntity. I can see from the XML that the ContactEntityFactory is registered as the factory to recreate the relatives, but I figured that by specifying the PersonEntityFactory in the PrefetchPath2, it would use that as factory for serialization as well.
Edit: Sorry not true. It throws the exception in EntityFields2 line 333 and looking at this._entityFields I can only see the fields that are specified in ContactEntity.
Is this the correct behaviour for EntityCollections? Is it not supposed to use the PersonEntityFactory for the collection when I explicitly specify it, or have I misunderstood something?
As I said, it works perfectly when not using TargetPerEntityHierarchy...
Any thoughts on this will be very much appreciated.
Regards Lau
ps. Other than a few hurdles with inheritance and serialization, I really love LLBLGen. I think it is a great product and would not hesitate to recommend it.
The relation was defined between super types (ContactEntity to ContactEntity via ContactRelationEntity) To limit the returned entities to those of type PersonEntity, you should pass a filter of that type in the prefetchPath, please try the following:
IRelationPredicateBucket filter = new RelationPredicateBucket(ContactFields.ContactId == id);
// Make PersonEntity our starting point
IPrefetchPath2 path = new PrefetchPath2((int)EntityType.PersonEntity);
// Load all related contacts - 1 in this test case (Make sure that we use the right factory (PersonEntityFactory)
path.Add(PersonEntity.PrefetchPathContactRelation).SubPath.Add(ContactRelationEntity.PrefetchPathRelatedContact, 0, PersonEntity.GetEntityTypeFilter(false));
PersonEntity entity;
using (DataAccessAdapter adapter = new DataAccessAdapter())
{
// Make sure to use the PersonEntityFactory so serialization works for the entity (subentity)
entity = adapter.FetchNewEntity(new PersonEntityFactory(), filter, path) as PersonEntity;
}
return entity;
(edit) You may also check the generated SQL query for the prefetchPath to see if there is anything wrong in the query. Refer to the LLBLGen Pro manual: "Using the generated code -> Troubleshooting and debugging"
Joined: 07-Mar-2007
Hi Walaa
Thank you for the reply - I think I have obscured my real question in the ramblings above so I will try to be more precise
The question is related to a previous question I had with inheritance and serialization:
In my previous question I populated a collection of Contacts (I know they are actually of suptype Person) and then transmitted them over WCF netTcpBinding. The client would not deserialize them as Contact and failed. The solution was to specify the PersonEntityFactory when populating the collection and that worked like a charm.
Now in this case I am populating a collection based on a prefetch path and got the same problem during deserialzation on the client. Again I know for sure that the fetched entities are actually of type PersonEntity. The fetch itself works perfectly, and I get exactly what I want.
Now my guess is that because the relation is defined between super types (as you said Walaa) the ContactRelationFactory does not know how to deserialize PersonEntity only ContactEntity and therefore it fails.
I tried to handle this by specifying the PersonEntityFactory in the subpath of relation, believing that it would solve the problem, but it did not - the factory is not part of the message that is transmitted from server to client.
A note on WCF and netTcpBinding is that it uses the XML Compact form when serializing as it prioritize IXmlSerializable over ISerializable - altough I would prefer binary...
Now for the questions:
1) Is what I am trying to do actually possible, given that it uses XML Compact form for serialization?
2) Would it be possible to do it if I found a way to use proper binary serialization (eg through a surrogate type)?
(Hmm, I am not very good at keeping these posts short )
Regards Lau
Now I perfectly understand the issue.
In short: the factory passed to the prefetchPath doesn't get serialized.
I don't know if it should be serialized or not (design wise speaking).
I don't know the answer, I'll try to look it up.
Joined: 07-Mar-2007
Hi
I can answer question 2 myself now.
It is not pretty, but it works.
I have added a DataContractSurrogate to the DataContractSerializerOperationBehavior that takes all objects that can be assigned to EntityBase2 (for adapter) and creates an Entity2Surrogated class, Serializes (BinaryFormatter) the Entity and adds the resulting bytes to a byte[] in the Entity2Surrogated object.
The Enitity2Surrogated object is the returned to the DataContractSerializer.
Now one problem with this is that you have to adjust you MaxArrayLength readerQuota as you reach 16K pretty fast...
I have put a solution with two examples on http://mx.bakman.dk/Contact.zip: - Contact1: No inheritance just plain Entities - works every time - Contact2: Using inheritance - only works if using surrogate types
It contains an sql script to create the DB (ContactTest) and add test data, an LPG file and of couse the source.
The UseBinaryEntityEncoding attribute is what makes the difference. If it is removed from the IContact interface example 2 stops working.
(Edit) Just realized that if you remove the UseBinaryEntityEncoding, you need to add ServiceKnownType's to the IContact to make it work...
As far as I can gather from forums and discussion groups this issue arises due to WCF prioritizing IXmlSerializable over ISerializable.
I hope this is usefull.
Regards Lau
THe problem is with XML deserialization: every collection has to have the proper factory specified, otherwise it won't work: it then will use the factory it initially has and use that factory to produce new instances.
This goes wrong if you pass subtypes in a collection with the factory set to the supertype.
We are aware of this but there's not a real solution to this other than adding more information to the XML, which means that the XML will become more verbose, as the deserialization works like this: - factory creates new instance - instance' method to retrieve values from XML gets called and entity's xml is passed in.
IF there's type info, which is there in normal XML, though not in the compact xml option (which is used in services) that info is used to produce a new instance.
What we could do is use type identifiers and refer to them at the beginning of the XML, however that has other implications.
It is a concern however, so I've added it to the list of things to look into for v2.1, because it will require a breaking change and the XML layout will also be changed so we have to add additional features to select what you want on the config level. However that doesn't fix it for you today, but that's all I can do.
What you could try is passing the set of entities in a collection with the factory set to the type of the entities in the collection.
Joined: 07-Mar-2007
Hi
What you could try is passing the set of entities in a collection with the factory set to the type of the entities in the collection.
I thought that was what I did when specifying the factory as part of the prefetch path.
Anyway, at the moment I am going for my solution 2, which is to use surrogate types and then do the binary serialization manually for now (see the posted solution). It works ok, and a side effect is that I do not need to specify KnownType attributes all over the place.
Thank you for your time.
Regards Lau