简体   繁体   中英

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?

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

Disclaimer: I'm not very familiar with Breeze, but I have a guess of what might be happening.

From the Breeze documentation on navigation properties , it says it doesn't do lazy-loading of navigation properties. So, if the item.Children() data isn't already fully available locally, it won't go out and fetch the data. You need to explicitly request that the Children data be retreived inline with the rest of the payload in order to have this information locally. In OData in general, this is done with the $expand query parameter in the request URL. In Breeze (according to that documentation I linked to), it's done via the .expand() method like so:

var query = new breeze.EntityQuery()
              .from("OrgObjects")
              .expand("Children");

@Jowa - thanks for your efforts to bring SAP and BreezeJS together.

You discovered expand which is the way to eagerly load the children of the parent OrgObject .

You can load them on-demand via loadNavigationProperty ... when the navigation property is working properly ... although that would be terribly inefficient to do one-by-one for each query result. This situation calls for expand .

Apparently Breeze is struggling with your navigation property because it can't identify the foreign key in the child entity that supports the relationship.

You do not have to have navigation properties defined for both sides of a 1..m relationship. The side most often omitted is the 1->N side (parent->child) property. For example, most of us would omit Gender.Person because we don't want the collection male.Persons .

In your case, if I understand correctly, you think the 1->N ( OrgObject.Children ) is defined but not the N->1 navigation ( Child.OrgObject ). Well that should work too!

But no matter what, for navigation properties to work in either direction, the metadata has to identify the Foreign Key (FK) property in the child entity type.

For some reason, it seems Breeze is not getting that information.

Hacking BreezeJS itself ... clever as that is ... should not be necessary.

We'd like to help. We'd need the following information:

  • Child class definition on the server ... highlighting its OrgObject FK property
  • The OrgObject - and Child -related metadata from the metadataStore object
  • The OrgObject - and Child -related JSON from the metadata file coming over the wire

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM