Question

I'm setting up a SPA using Angular and Breeze. I've been following John Papa's hot-towel tutorial from plural site. I'm having a weird issue I think might be spawning from my metadata? But in the end, I'm not really sure....

First things first, my API is running off a LAMP stack, so I'm not using EF. I have created a Metadata endpoint that I think is giving me the correct structure I need. I'm using the breeze.angular.q.js to help my mappings from Q to $q

resource: api/v1/Metadata

{
    "metadataVersion": "1.0.5",
    "dataServices": [
        {
            "serviceName": "api/v1/",
            "hasServerMetadata": true,
            "jsonResultsAdapter": "webApi_default",
            "useJsonp": false
        }
    ],
    "structuralTypes": [
        {
            "shortName": "tracks",
            "namespace": "MyNamespace",
            "dataProperties": [
                {
                    "name": "id",
                    "nameOnServer": "id",
                    "maxLength": 36,
                    "validators": [],
                    "dataType": "Guid",
                    "isPartOfKey": true
                },
                {
                    "name": "title",
                    "nameOnServer": "title",
                    "maxLength": 255,
                    "validators": [],
                    "dataType": "String"
                },
                {
                    "name": "description",
                    "nameOnServer": "description",
                    "maxLength": 0,
                    "validators": [],
                    "dataType": "String"
                }
            ]
        }
    ]
}

example API return data looks like this:

resource: api/v1/tracks

{
    "data": [
        {
            "id": "495f21d6-adfc-40b6-a41c-fc93d9275e24",
            "title": "harum",
            "description": "Error doloribus ipsam et sunt fugiat."
        },
        {
            "id": "d7b141d2-6523-4777-8b5a-3d47cc23a0fe",
            "title": "necessitatibus",
            "description": "Voluptatem odit nulla maiores minima eius et."
        }
    ],
    "embeds": [
        "courses"
    ]
}

Now w/ all my code, I'm actually returning correct data from my api. I've poured over examples from the breeze site as a few good tidbits I found here on SO (like this question and great answer from ward). Alas, no luck. Essentially whats happening is when I try to loop over my results, in my view model, that are returned from my breeze query, I get an angular error Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: t in vm.tracks, Duplicate key: object:00I

The call is happening in a function inside my datacontext. The data returned in my querySucceeded promise callback doesn't appear to be correctly bound.

datacontext.js

...
function getTrackPartials() {
    ...
    return EntityQuery.from(entityNames.track)
        .toType(entityNames.track)
        .using(manager).execute()
        .then(querySucceeded, _queryFailed);

    function querySucceeded(data) {
        console.log(data);  // <--  Log out to see what is returned
        tracks = data.results;
        _areTracksLoaded(true)
        log('Retrieved [Track Partials] from remote data source', tracks.length, true);
        return tracks;
    }
}

If I were to log this data out to the console, I get this (all the $$hashKey's are the same, and the id, title, description are all NULL. But I do get the correct number of results, and this isn't a coincidence - if I adjust the number of results I'm supposed to receive, it correctly matches every time).

querySucceeded promise callback

Now, since my data comes back a little bit different - I used the Edmonds example and created a custom JsonResultsAdapter so I could "massage" the data. Its very rudimentary at the moment, as I'm just trying to get this working. Whats really throwing me off, is if I log out the node parameter from the visitNode function in the JsonResultsAdapter, it has the correct data....????

entityManagerFactory.js

(function () {
    'use strict';

    var serviceId = 'entityManagerFactory';
    angular.module('app').factory(serviceId, ['config', emFactory]);

    function emFactory(config) {
        breeze.config.initializeAdapterInstance('modelLibrary', 'backingStore', true);
        breeze.NamingConvention.camelCase.setAsDefault();

        var serviceName = config.remoteServiceName;
        var metadataStore = new breeze.MetadataStore();

        var provider = {
            metadataStore: metadataStore,
            newManager: newManager
        };

        var jsonResultsAdapter = new breeze.JsonResultsAdapter({
            name: "Tracks",
            extractResults: function(json) {
                console.log(json.results.data);  // <--  Log out to see what is returned
                return json.results.data;
            },
            visitNode: function(node, mappingContext, nodeContext) {
                console.log(node);  // <--  Log out to see what is returned
                return {
                    entityType: 'tracks',
                    nodeId: node.id
                };
            }
        });

        var dataService = new breeze.DataService({
            serviceName: serviceName,
            jsonResultsAdapter: jsonResultsAdapter
        });

        return provider;

        function newManager() {
            var mgr = new breeze.EntityManager({
                dataService: dataService,
                metadataStore: metadataStore
            });

            return mgr
        }
    }
})();

Here is my return value from my JsonResultsAdapter::extractResults function JsonResultsAdapter extractResults return value

Here is a node from my JsonResultsAdapter::visitNode function JsonResultsAdapter visitNode node parameter

Any help would be appreciated. Like I said, I'm not really sure where the error is happening at? But if i had to guess, I would say there is some disconnect between my EntityQuery using my manager and the JsonResultsAdapter, that might be caused by bad metadata I've generated.

** UPDATE **

So I walked through the breeze code to figure out where I'm loosing my data and was able to figure out whats going on and a way to fix it. However, I'm not sure if thats the best way to actually handle this.

I should mention, I used bower to install breeze - and by doing such I went the bower-breeze-angular git://github.com/eggers/bower-breeze-angular.git package and not the default breeze breeze git://github.com/IdeaBlade/Breeze.git which is bloated with examples and other data I wasn't keen on packing into my repo.

In breeze, after my JsonResultsAdapter::visitnode callback has returned, it needs to "merge" my data, the problem I have is the entityKey that is returned from my node is not matching up. This is because the rawValueFn from my mappingContext is looking for nameOnServer, which I thought I set in my metadata from my server - but somehow when I log out my dataproperty it has changed from what I set.

This is one dp logged out, if you look back up at the top in my Metadata resource call, I specifically set this to "id". How did this change to Id? Thats whats causing my headache!

DataProperty with changed nameOnServer Key

I can get around this by updating my rawValueFn function on my mappingContext in my JsonResultsAdapter and everything will work - but this feels like a "hack". I've also tried playing w/ the "NamingConvention" but that doesn't seem to work either.

Here is my updated JsonFactory that makes it work

    var jsonResultsAdapter = new breeze.JsonResultsAdapter({
        name: "Tracks",
        extractResults: function(json) {
            return json.results.data;
        },
        visitNode: function(node, mappingContext, nodeContext) {

            // Had to adjust this so it would lowercase and correctly match
            mappingContext.rawValueFn = function(rawEntity, dp) {
                name = dp.name;
                name.substring(0, 1).toLowerCase() + name.substring(1);
                return rawEntity[name];
            }

            return {
                entityType: 'tracks'
            };
        }   
    }); 
Was it helpful?

Solution

Wow this is a long question.

First, I recommend that you look at the "Metadata by hand" topic which describes a much simpler way to define your metadata ... using the Breeze Labs metadata helper. That will cut down a lot of the tedium and make it much clearer to read and understand.

Second, do NOT specify "jsonResultsAdapter" in your metadata. It looks to me like your pinning your metadata to the WebAPI adapter when, in fact, you want to use a different one. Do NOT specify the "namingConvention" in your metadata either as that will trump whatever you set elsewhere. And given that you are not getting metadata from the server, "hasServerMetadata" should be false if you bother to set it at all (which you shouldn't).

Third, stick to the client-side name and forget about "nameOnServer". The NamingConvention is going to crush that anyway.

Fourth, if (as it appears) the client-side and server-side property names are BOTH camelCase DO NOT change the NamingConvention default! You don't want any translation. The default does no translation.

If I'm correct about that, do NOT change the NamingConvention to camelCase! The "camelCase" convention tells Breeze "the server is PascalCase so translate my client-side camel case property names into Pascal names on the server". If I understand correctly, you don't want client-side "id" to become server-side "Id" ... which is what will happen. That's why (I believe) you are seeing "Id" as the "nameOnServer".

Fifth, within a JsonResultsAdapter, the node names match the JSON from your server and, therefore, are server-side names. Keep them that way. The NamingConvention will convert them to client-side names when it translates node property values to entity property values. In fact, you'll lose data if you mistakenly use client-side names on the nodes.

Do you have some need to mutate property names and values in the JSON as it arrives from the server? If not, don't mess with those names in your visitNode method. About all you'll need to do is make sure that you identify the correct EntityType for the node and return that in the result.

Sixth, I'm pretty sure the "entityType" property of the visitNode result must be an actual EntityType, NOT the name of the type as you have shown in your example. You can't say

return {
   entityType: 'tracks',
};

You have to give it the real type (I'm pretty sure)

return {
   entityType: trackType,
};

Look at the other Breeze adapters (e.g., the Web API adapter). It gets the EntityType from the MetadataStore.

Seventh Why are you setting the "nodeId"? I'm not saying you're wrong to do so. But you should know why. The "nodeId" is used to help reconstruct an object graph when the same entity appears multiple times in the payload. It is only useful when accompanied by a "nodeRefId" in some other node which points to a "nodeId" value. At this point, where you only have one kind of entity and not relationships, setting the "nodeId" isn't accomplishing anything. Later it will ... but only if you're setting it with a value that makes sense.

What I think you're doing is setting the "nodeId" to the primary key value of Track. Isn't that what node.id is in your case? If I'm right, don't do that. The "nodeId" is NOT the PK of your entity. It is a marker for serializing entity graphs with cycles.

Wow mine is a long answer

I fear you are a bit over your head here. Writing a dataService adapter or a jsonResultsAdapter is not a Breeze beginner task. If you're going to go there, please study the existing adapters and follow them meticulously. Know why they do what they do rather than wing it.

I hope I've provided a few clues here.

I suspect it is much simpler than you're making it. Some key thoughts:

  • Make sure you aren't changing the NamingConvention unless you really do need to change the spelling of property names from client-to-server.

  • Set the "entityType" in your JsonResultsAdapter to an EntityType, not the name of the type.

  • Don't rewrite breeze functions like rawValueFn; you'll break Breeze and you won't know how or why.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top