javascriptodatabreezesap-gateway

Read odata link in BreezeJS


I'm trying to get the BreezeJS library working with an SAP OData service. This is working for reading entities, but I'm not able to resolve the linked objects.

My EntityType is an OrgObject.

<EntityType Name="OrgObject" sap:content-version="1">
  <!-- ... -->
  <NavigationProperty Name="Children" Relationship="ZGW_ORGSTRUCTURE.OrgObject_To_Children" FromRole="FromRole_OrgObject_To_Children" ToRole="ToRole_OrgObject_To_Children"/>
</EntityType>

I have a link to resolve all linked OrgObjects (link is named Children).

<Association Name="OrgObject_To_Children" sap:content-version="1">
  <End Type="ZGW_ORGSTRUCTURE.OrgObject" Multiplicity="1" Role="FromRole_OrgObject_To_Children"/>
  <End Type="ZGW_ORGSTRUCTURE.OrgObject" Multiplicity="*" Role="ToRole_OrgObject_To_Children"/>
</Association>

So, this breeze query is working:

var query = new breeze.EntityQuery().from("OrgObjects");
manager.executeQuery(query).then(function(data) {
  data.results.forEach(function(item) {
    console.log(item);
  });
}).fail(/*...*/);

How do I call the "children" from this object?

Attempt 1:

var query = new breeze.EntityQuery().from("OrgObjects");
manager.executeQuery(query).then(function(data) {
  data.results.forEach(function(item) {
    console.log(item);
    // ...
    var Children = item.Children();
    // ...
  });
}).fail(/*...*/);

this results in an error:

message: "Object [object Object] has no method 'children'"

Attempt 2:

var query = new breeze.EntityQuery().from("OrgObjects");
manager.executeQuery(query).then(function(data) {
  data.results.forEach(function(item) {
    console.log(item);
    // ...
    item.entityAspect.loadNavigationProperty("Children").then(function(data) {
      console.log(data.results);
      data.results.forEach(function(item) {
        console.log(item);
      });
    }).fail(function(e) {
      console.log(e);
    });
    // ...
  });
}).fail(/*...*/);

This results in an error:

The 'propertyOrExpr' parameter must be a 'string'

Attempt 3:

var query = new breeze.EntityQuery().from("OrgObjects").expand("Children");
manager.executeQuery(query).then(function(data) {
  data.results.forEach(function(item) {
    console.log(item);
    // ...
    console.log(item.Children);
    console.log( item.Children.length );
    // ...
  });
}).fail(/*...*/);

Result: item.Children is an object. But item.Children.length = 0. When I check the http response, the children are fetched from the server, but not available in the item.Children object.

Console output:

Finance Department
[parentEntity: Object, navigationProperty: ctor, arrayChanged: ctor, _addsInProcess: Array[0], push: function…]
    _addsInProcess: Array[0]
    _getEventParent: function () {
    _getPendingPubs: function () {
    arrayChanged: ctor
    length: 0
    load: function (callback, errorCallback) {
    navigationProperty: ctor
    parentEntity: Object
    pop: function () {
    push: function () {
    shift: function () {
    splice: function () {
    unshift: function () {
    __proto__: Array[0]
0

Who can help me out? Is there something missing in my OData service?


Solution

  • Problem is "solved".

    1->n relations in breeze can only be used if the inverse property (n->1) is defined. As far as I know it is not possible in SAP Gateway to define an inverse relation. To test it I created 2 relationships:

    <Association Name="OrgObject_To_Children" sap:content-version="1">
        <End Type="ZGW_ORGSTRUCTURE.OrgObject" Multiplicity="1" Role="FromRole_OrgObject_To_Children"/>
        <End Type="ZGW_ORGSTRUCTURE.OrgObject" Multiplicity="*" Role="ToRole_OrgObject_To_Children"/>
    </Association>
    <Association Name="OrgObject_To_Children_inverse" sap:content-version="1">
        <End Type="ZGW_ORGSTRUCTURE.OrgObject" Multiplicity="*" Role="FromRole_OrgObject_To_Children_inverse"/>
        <End Type="ZGW_ORGSTRUCTURE.OrgObject" Multiplicity="1" Role="ToRole_OrgObject_To_Children_inverse"/>
    </Association>
    

    Then I changed this in breezejs

    function updateCrossEntityRelationship(np) {
        var metadataStore = np.parentType.metadataStore;
        var incompleteTypeMap = metadataStore._incompleteTypeMap;
    
        // ok to not find it yet
        var targetEntityType = metadataStore._getEntityType(np.entityTypeName, true);
        if (targetEntityType) {
            np.entityType = targetEntityType;
        }
    
        var assocMap = incompleteTypeMap[np.entityTypeName];
        if (!assocMap) {
            addToIncompleteMap(incompleteTypeMap, np);
        } else {
            var inverse = assocMap[np.associationName];
    
            /* enable inverse relationship for SAP GATEWAY */
            // search for inverse links
            if( !inverse ){
                var associationName = "";
                if(np.associationName.indexOf("_inverse") > 0){
                    associationName = np.associationName.replace("_inverse", "");
                }else{
                     associationName = np.associationName + "_inverse";
                }
                inverse = assocMap[associationName];
            }
            /* end enable inverse relationship for SAP GATEWAY */
    
            if (inverse) {
                removeFromIncompleteMap(incompleteTypeMap, np, inverse);
            } else {
                addToIncompleteMap(incompleteTypeMap, np);
            }
        }
    };
    

    But that didn't solve everything, there was an other thing I had to change to get it work. Don't know if this is a bug or if it is related to SAP Gateway.

    function mergeRelatedEntitiesCore(rawEntity, navigationProperty, parseContext) {
        var relatedRawEntities = rawEntity[navigationProperty.nameOnServer];
        if (!relatedRawEntities) return null;
    
        // needed if what is returned is not an array and we expect one - this happens with __deferred in OData.
        /* original code 
        if (!Array.isArray(relatedRawEntities)) return null;
    
        var relatedEntities = relatedRawEntities.map(function(relatedRawEntity) {
            return visitAndMerge(relatedRawEntity, parseContext, { nodeType: "navPropItem", navigationProperty: navigationProperty });
        });
    
        return relatedEntities;
        end original code */
    
        if (!Array.isArray(relatedRawEntities.results)) 
            return null;
    
        relatedRawEntities.results = relatedRawEntities.results.map(function(relatedRawEntity) {
            return visitAndMerge(relatedRawEntity, parseContext, { nodeType: "navPropItem", navigationProperty: navigationProperty });
        });
    
        return relatedRawEntities.results;
    
    }
    

    I only tested this for reading operations. Later on I'll check the update operations.

    kr, Joachim