AngulatJS / Prefetch Path Linq Query

Posts   
 
    
jhowes
User
Posts: 12
Joined: 18-Jun-2014
# Posted on: 18-Jun-2014 17:39:38   

Please see the JSON generated below:

I am using latest version of LLBLGEN with the attributes (DataContract/DataMember etc..) from this URL:

https://www.llblgen.com/documentation/4.0/LLBLGen%20Pro%20RTF/Using%20the%20generated%20code/Adapter/Distributed%20systems/gencode_webservices.htm

My code is dirt simple from the LINQ example...


        public IEnumerable<SearchContentEntity> GetSearchContent2()
        {
            using (var adapter = new DataAccessAdapter())
            {
                var metaData = new LinqMetaData(adapter);
                
                return metaData.SearchContent.WithPath(p => p.Prefetch(x => x.SearchContentType)).ToList(); 
            }
        }

When the data comes back it is adding a Contents property under SearchContentType which contains a full reference of all of the parent types that match that ID. When I receive those I cannot parse them into my model. The entities are buried under the type so they show up blank.

Here is the JSON

Is there a way to generate the Entities so that everything is essentially in-line?

Thanks in advance for any advice/guidance!

[ { "$id": "1", "Enabled": true, "SearchContentAuthor": "neww", "SearchContentEffectiveEndDate": null, "SearchContentEffectiveStartDate": null, "SearchContentId": 9, "SearchContentPubDate": "2014-06-10T16:08:32.217", "SearchContentShortDesc": "http://localhost/Fulltext.MVC/GIS/Viewer/index.html?layerName=PUBLIC_SCHOOLS&serviceName=Adminstrative", "SearchContentSortOrder": 2, "SearchContentText": "1", "SearchContentTitle": "Public Schools Updated ", "SearchContentTypeId": 2, "SearchContentType": { "$id": "2", "SearchContentTypeDesc": "Updated", "SearchContentTypeId": 2, "SearchContents": [ { "$ref": "1" }, { "$id": "3", "Enabled": true, "SearchContentAuthor": "NONE", "SearchContentEffectiveEndDate": null, "SearchContentEffectiveStartDate": null, "SearchContentId": 10, "SearchContentPubDate": "2014-06-10T16:08:32.217", "SearchContentShortDesc": "http://localhost/Fulltext.MVC/GIS/Viewer/index.html?layerName=PUBLIC_SCHOOLS&serviceName=Adminstrative", "SearchContentSortOrder": 3, "SearchContentText": "2", "SearchContentTitle": "Public Schools Updated", "SearchContentTypeId": 2, "SearchContentType": { "$ref": "2" } }, { "$id": "4", "Enabled": true, "SearchContentAuthor": "", "SearchContentEffectiveEndDate": null, "SearchContentEffectiveStartDate": null, "SearchContentId": 11, "SearchContentPubDate": "2014-06-10T16:08:32.217", "SearchContentShortDesc": "http://localhost/Fulltext.MVC/GIS/Viewer/index.html?layerName=PUBLIC_SCHOOLS&serviceName=Adminstrative", "SearchContentSortOrder": 4, "SearchContentText": "3", "SearchContentTitle": "Public Schools Updated", "SearchContentTypeId": 2, "SearchContentType": { "$ref": "2" } }, { "$id": "5", "Enabled": true, "SearchContentAuthor": "", "SearchContentEffectiveEndDate": null, "SearchContentEffectiveStartDate": null, "SearchContentId": 12, "SearchContentPubDate": "2014-06-10T16:08:32.217", "SearchContentShortDesc": "http://localhost/Fulltext.MVC/GIS/Viewer/index.html?layerName=PUBLIC_SCHOOLS&serviceName=Adminstrative", "SearchContentSortOrder": 5, "SearchContentText": "4", "SearchContentTitle": "Public Schools Updated", "SearchContentTypeId": 2, "SearchContentType": { "$ref": "2" } } ] } }, { "$ref": "3" }, { "$ref": "4" }, { "$ref": "5" }, { "$id": "6", "Enabled": true, "SearchContentAuthor": "", "SearchContentEffectiveEndDate": null, "SearchContentEffectiveStartDate": null, "SearchContentId": 13, "SearchContentPubDate": "2014-06-10T16:08:32.217", "SearchContentShortDesc": "http://localhost/Fulltext.MVC/GIS/Viewer/index.html?layerName=PUBLIC_SCHOOLS&serviceName=Adminstrative", "SearchContentSortOrder": 6, "SearchContentText": "5", "SearchContentTitle": "Public Schools Updated", "SearchContentTypeId": 1, "SearchContentType": { "$id": "7", "SearchContentTypeDesc": "Summary", "SearchContentTypeId": 1, "SearchContents": [ { "$ref": "6" }, { "$id": "8", "Enabled": true, "SearchContentAuthor": "J. Mock", "SearchContentEffectiveEndDate": null, "SearchContentEffectiveStartDate": null, "SearchContentId": 14, "SearchContentPubDate": "0001-01-01T00:00:00", "SearchContentShortDesc": "", "SearchContentSortOrder": 99, "SearchContentText": "None", "SearchContentTitle": "Private Schools Update", "SearchContentTypeId": 1, "SearchContentType": { "$ref": "7" } } ] } }, { "$ref": "8" } ]

Walaa avatar
Walaa
Support Team
Posts: 14993
Joined: 21-Aug-2005
# Posted on: 18-Jun-2014 18:19:50   

The relation between SearchContent and SearchContentType is m:1 correct?

Which version (build no.) of the LLBLGen Pro runtime library are you using?

jhowes
User
Posts: 12
Joined: 18-Jun-2014
# Posted on: 18-Jun-2014 18:44:15   

Yes the relationship is m:1

Build info should be latest build Version 4.1.14.219

Walaa avatar
Walaa
Support Team
Posts: 14993
Joined: 21-Aug-2005
# Posted on: 18-Jun-2014 21:34:36   

How do you serialize the fetched graph?

jhowes
User
Posts: 12
Joined: 18-Jun-2014
# Posted on: 18-Jun-2014 22:23:57   

            var json = config.Formatters.JsonFormatter;
            json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;
            json.SerializerSettings.ContractResolver = new DefaultContractResolver()
            {
                IgnoreSerializableInterface = true,
                IgnoreSerializableAttribute = true
            };

            var appXmlType = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(t => t.MediaType == "application/xml");
            config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType); 

is what I am using. I am going to post a small snap shot of my project with DB included in a sec.

btw the compact xml 25 looks exactly like the JSON. If I view graph in either I see the same results; with a contents node under my child with links back to the parent via the $ref{id} ID.

Thanks!

jhowes
User
Posts: 12
Joined: 18-Jun-2014
# Posted on: 18-Jun-2014 23:14:35   

Let me know if there is a location to send this project to.

It is woefully over the size limit. ~10 MB

Thanks!

jhowes
User
Posts: 12
Joined: 18-Jun-2014
# Posted on: 18-Jun-2014 23:17:05   

confused

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 19-Jun-2014 07:44:20   
  • Please show the code where you are actually serializing the graph and deserializing back.

  • 10MB is a lot for a repro solution. Please try to cook a very simple reproducible piece of code, something we can understand and run quickly. Try to a standard DB like Northwind to reproduce the problem.

  • And, tell us what exactly isn't right from the json you posted in the 1st post. I think is the fact that you are fetching A->B (A entities with prefetchpath B). But the json looks like A->B->As, right? Or, what is exactly that you expected and didn't happen?

David Elizondo | LLBLGen Support Team
jhowes
User
Posts: 12
Joined: 18-Jun-2014
# Posted on: 19-Jun-2014 17:04:54   

I am using Web API so I believe that is doing the serialization. I am simply sending back whatever I get back (entities)

In the original (production) case I am sending back an entity collection, and in the test case I am sending back IENumerable just like the example for LINQ/LLBLGEN

I will try to repo using Northwind/AdventureWorks but to produce this exactly I have to add all of the MVC/Web Api libs which bloats the project considerably.

You are correct in that I want A-->B, but it is somehow sending back A-->B-->A

As I stated before I am getting the same behavior when I send back XML rather than JSON. A-->B-->A.

I will setup a simple project and see what I can come up with.

Thanks!

Walaa avatar
Walaa
Support Team
Posts: 14993
Joined: 21-Aug-2005
# Posted on: 19-Jun-2014 20:01:56   

While you are cooking a repro, please check this thread, for more details about entities-JSON serialization in WebAPI. https://www.llblgen.com/tinyforum/Messages.aspx?ThreadID=22731

jhowes
User
Posts: 12
Joined: 18-Jun-2014
# Posted on: 19-Jun-2014 20:51:13   

For clarification my entities are being fetched fine through Web API if I do not include a prefetch path.

Once I do that it begins to invoke A-->B-->A references. The entities being fetched before serialization which is done from the webapi look fine.

I have been using this mechanism for months but once I added the prefetch path which now includes the child object with the 'Contents' node which has all of the parent types that also match.

jhowes
User
Posts: 12
Joined: 18-Jun-2014
# Posted on: 19-Jun-2014 21:38:01   

I have bundled a sample project and a small DB.

Two tables.. (parent/child) foreign key ChildId (mandatory / non-identifying)

I have this code in my WebApiConfig.cs file... This is from your site. I added the ReferenceLoopHandling Ignore (no effect).


            var json = config.Formatters.JsonFormatter;
            json.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
            json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;
            json.SerializerSettings.ContractResolver = new DefaultContractResolver()
            {
                IgnoreSerializableInterface = true,
                IgnoreSerializableAttribute = true
            };

            var appXmlType = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(t => t.MediaType == "application/xml");
            config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType); 

The LLBLGEN project is setup with the DataContract--> Entity, DataMember-->NormalField,NavigatorSingleValue, NavigatorCollection for Attributes. Once I added the system.runtime.serialization reference the LLBLGEN project would compile.

this line: json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;

when switched to json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Arrays;

seems to give me what I am expecting. I have only looked at the raw JSON, and I have not tried to use it in my Model. It does not include the refs. A-->B-->A

Here is the sample JSON that does not work..

[ { "$id": "1", "ChildId": 1, "ParentDesc": "article 1", "ParentId": 1, "Child": { "$id": "2", "ChildDesc": "Update", "ChildId": 1, "Parents": [ { "$ref": "1" }, { "$id": "3", "ChildId": 1, "ParentDesc": "article 2", "ParentId": 2, "Child": { "$ref": "2" } } ] } }, { "$ref": "3" }, { "$id": "4", "ChildId": 2, "ParentDesc": "article3", "ParentId": 3, "Child": { "$id": "5", "ChildDesc": "New", "ChildId": 2, "Parents": [ { "$ref": "4" }, { "$id": "6", "ChildId": 2, "ParentDesc": "article4", "ParentId": 4, "Child": { "$ref": "5" } } ] } }, { "$ref": "6" }, { "$id": "7", "ChildId": 3, "ParentDesc": "article5", "ParentId": 5, "Child": { "$id": "8", "ChildDesc": "Summary", "ChildId": 3, "Parents": [ { "$ref": "7" }, { "$id": "9", "ChildId": 3, "ParentDesc": "article6", "ParentId": 6, "Child": { "$ref": "8" } } ] } }, { "$ref": "9" } ]

Attachments
Filename File size Added on Approval
small.zip 75,735 19-Jun-2014 21:38.10 Approved
smallDB.zip 305,862 19-Jun-2014 21:38.22 Approved
daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 20-Jun-2014 08:21:59   

I can't compile your project due to references. Let me try to reproduce it with my own code.

David Elizondo | LLBLGen Support Team
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39861
Joined: 17-Aug-2003
# Posted on: 20-Jun-2014 11:17:08   

So in short, you want A to ref B but B not to ref A? Do realize the entities are referencing each other (one through a collection).

Frans Bouma | Lead developer LLBLGen Pro
jhowes
User
Posts: 12
Joined: 18-Jun-2014
# Posted on: 22-Jun-2014 00:49:18   

First off sorry 'bout the references in the project I sent. Wrapping this around the web api adds a ton of bloat.

I think I found where I am having a bit of trouble...

I am aware of the references in the collection, but they way they are handled once serialized is a bit different than what I am used to.

I am used to using Entities from a server side perspective. Once I set a break point and look at the entity and also the collection I can see that everything is populated. By this I mean that, if I look at them I can see that both the parent entity is populated as well as the Child AND reference back to the parent. That is why I am slightly confused I guess. I am expecting the same behavior once it is serialized to JSON, but that is not what is happening.

It will write the parent as fast as possible in the graph, which in the prefetch if a Parent-->Child-->[Parent, Another Parent] is present it will write the [Another parent] under the Child that references it, and when the 'another parent' is supposed to be written out it will create a reference to the first instance of that 'another parent' in the graph. This is what is breaking when I am sending it back to the client. Since the client is expecting a list of objects it gets to where the 'another parent' is supposed to be all it sees is a ref and not the actual content of the entity.

so I am getting (in the JSON)

Parent (Full Entity) ChildType Parent (ref) Another Parent (full entity) Another Parent (ref)

What I want (hopefully possible) is

Parent (Full Entity) ChildType Parent (ref) Another parent (ref) Another Parent (Full Entity)

This can be duplicated very easily by setting up a web api project and adding one Web API controller. In the 'GET' of that controller add the following code..


            using (var adapter = new DataAccessAdapter())
            {
                var metaData = new LinqMetaData(adapter);


                return metaData.Parent.WithPath(p => p.Prefetch(x => x.Child)).ToList();
            }

and in the following in the WebApiConfig.cs


            var json = config.Formatters.JsonFormatter;
            json.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
            json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;
            json.SerializerSettings.ContractResolver = new DefaultContractResolver()
            {
                IgnoreSerializableInterface = true,
                IgnoreSerializableAttribute = true
            };

            var appXmlType = config.Formatters.XmlFormatter.SupportedMediaTypes.FirstOrDefault(t => t.MediaType == "application/xml");
            config.Formatters.XmlFormatter.SupportedMediaTypes.Remove(appXmlType); 

I can agree that everything is coming back, but the way it is coming back is not in the order that I need when projecting into my model. If I remove the prefetch, everything works as expected.

Thanks again for taking some time to look at this. I have been a customer for quite some time and your stuff rocks!

Walaa avatar
Walaa
Support Team
Posts: 14993
Joined: 21-Aug-2005
# Posted on: 23-Jun-2014 22:03:06   

Unfortunately what you want is not possible to achieve. We use depth-first-search traversal of the graph, while you are asking for a breadth-first-search technique.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39861
Joined: 17-Aug-2003
# Posted on: 24-Jun-2014 09:50:30   

Explanation here: http://www.llblgen.com/tinyforum/GotoMessage.aspx?MessageID=128838&ThreadID=21714

The explanation is for xml, but it's the same for JSon. I think though that the depth-first-search progression is done by the json serializer so there's little we can do.

Frans Bouma | Lead developer LLBLGen Pro
jhowes
User
Posts: 12
Joined: 18-Jun-2014
# Posted on: 03-Jul-2014 14:43:55   

Frans,

Yep you are spot on. After looking into this more it appears that the culprit here is the Web API and specifically the serializer.

Three options are either create proxy classes, manipulate the JSON once it gets to the client, or remove prefetch paths and lazy load.

This seems to be a known issue with client side frameworks (most notably Knockout and AngularJS).

I decided to manipulate the JSON. Here is my controller for anyone interested...

Source: http://stackoverflow.com/questions/15312529/resolve-circular-references-from-json-object/15757499#15757499


                $scope.resolveReferences = function(json) {
                    if (typeof json === 'string')
                        json = JSON.parse(json);

                    var byid = {}, // all objects by id
                        refs = []; // references to objects that could not be resolved
                    json = (function recurse(obj, prop, parent) {
                        if (typeof obj !== 'object' || !obj) // a primitive value
                            return obj;
                        if (Object.prototype.toString.call(obj) === '[object Array]') {
                            for (var i = 0; i < obj.length; i++)
                                if ("$ref" in obj[i])
                                    obj[i] = recurse(obj[i], i, obj);
                                else
                                    obj[i] = recurse(obj[i], prop, obj);
                            return obj;
                        }
                        if ("$ref" in obj) { // a reference
                            var ref = obj.$ref;
                            if (ref in byid)
                                return byid[ref];
                            // else we have to make it lazy:
                            refs.push([parent, prop, ref]);
                            return;
                        } else if ("$id" in obj) {
                            var id = obj.$id;
                            delete obj.$id;
                            if ("$values" in obj) // an array
                                obj = obj.$values.map(recurse);
                            else // a plain object
                                for (var prop in obj)
                                    obj[prop] = recurse(obj[prop], prop, obj);
                            byid[id] = obj;
                        }
                        return obj;
                    })(json); // run it!

                    for (var i = 0; i < refs.length; i++) { // resolve previously unknown references
                        var ref = refs[i];
                        ref[0][ref[1]] = byid[ref[2]];
                        // Notice that this throws if you put in a reference at top-level
                    }
                    return json;
                }

Walaa avatar
Walaa
Support Team
Posts: 14993
Joined: 21-Aug-2005
# Posted on: 03-Jul-2014 16:31:39   

Thanks for the feedback, I'm sure it will come handy to others. Also it would be great if you can blog about it.