Skip to Content

Multi-Level Navigation in OData-Service

Hey Experts,

at the moment im trying to develop an OData-Service with Multi-Level Assocations. For those which are familiar with the Tables BUT000 & BUT050 is here an Example what exactly i'm trying to achieve:

In my OData-Service im having the following Entity's:

- BusinessPartner (with Properties like "Name1,Name2, and so on...")

Key: PartnerNo.

NavigationProperties:

ToRelation (using Assocation ToRelation)

- Relation (with Properties like "RelationshipType, RelationshipNo, Partner1, Partner2, DateFrom, DateTo, and so on...")

Key: RelationshipNo, RelationshipType, Partner1, Partner2, DateFrom

NavigationProperties:

ToRelationPartner (using Assocation ToRelationPartner)

Assocations:

- ToRelation

SourceEntity: BusinessPartner

TargetEntity: Relation

Cardinality: 1 to 0..n

RefConstraint:

BusinessParter~ParterNo = Relation~Partner1


- ToRelationPartner

SourceEntity: BusinessPartner

TargetEntity: Relation

Cardinality: 1 to 1

RefConstraint:

BusinessPartner~PartnerNo = Relation~Partner2

The Target is that the following URI is working as expected:

/BusinessPartner('0123456789')/ToRelation(<insertKeyHere>)/ToRelationPartner

->which should have a result containing the "Partner2"-BusinessPartner Data from the corresponding Relation.

Another Example would be the following Link in the Northwind Service:

http://services.odata.org/v2/Northwind/Northwind.svc/Products(ProductID=2)/Supplier/Products

--> which shows all the Products of the Supplier from Product with Id 2

Im able to setup the whole Data Model inside SEGW, so far so good..

But while implementing the Logic inside, i faced a problem to get the right Key for my Data.

Lets say im using the URI from above:

/BusinessPartner('0123456789')/ToRelation(<insertKeyHere>)/ToRelationPartner

In the Debugger i stop at BusinessPartner_GetEntity.

Which is obviously correct, but when i now try to get the right Key all API-Methods from the Gateway won't give me the correct key.

GET_CONVERTED_SOURCE_KEYS -> will return the Key "Partner = 0123456789"

GET_CONVERTED_KEYS -> will return nothing, just the empty Structure of the BusinessPartner Entity

GET_KEYS -> Will also return just an empty table.

So i need to process the complete Navigation-Path by my own, and here it comes my questions:

Is this the right way to do this?

Is there no convenience method from the Gateway-API which processes the Nav-Path and give me the right keys?

Has anyone ever implemented something like this in an dynamic way and is able to help me here? :-)

I appreciate any kind of input. :)

Thanks for your help!

Sascha

Add comment
10|10000 characters needed characters exceeded

  • Get RSS Feed

3 Answers

  • Best Answer
    Jan 13 at 11:09 PM

    Hi Sascha,

    please check the implementation in the sample service GWSAMPLE_BASIC.

    The following request

    /sap/opu/odata/IWBEP/GWSAMPLE_BASIC/BusinessPartnerSet('0100000000')/ToSalesOrders('0500000000')/ToBusinessPartner

    works and the coding that resolves the navigation path using the statement io_tech_request_ctx_entity->get_navigation_path( ) as suggested by Ankit is handled in the method

    RESOLVE_NAVIGATION_PATH of the class /IWBEP/CL_GWSAMPLE_BAS_DPC_EXT.

    Please note that you have to generate the EPM demo data using transaction SEPM_DG beforehand.

    Best Regards,

    Andre

    Add comment
    10|10000 characters needed characters exceeded

    • Hi Andre,

      thanks for giving me the example. The example is definitely a good way to resolve my requirement in an "static" way, so i will mark this as correct. :)

      But i ended up writing an Method to retrieve the keys in a more generic way. (like an Utility-Method which can always resolve the Nav-Path even if the Data-Model changes)

      It doesnt support the full navigation until now, but im able to resolve my requirement for the following URI.

      /BusinessPartner('0100083750')/ToRelationA/ToRelationpartner 

      Please check the code below to see what i have done:

      * <SIGNATURE>---------------------------------------------------------------------------------------+
      
      * | Instance Private Method ZCL_ZBAR_BASIS_BP_DPC_EXT->GET_KEY
      
      * +-------------------------------------------------------------------------------------------------+
      
      * | [--->] IO_TECH_REQUEST_CONTEXT        TYPE REF TO /IWBEP/IF_MGW_REQ_ENTITY
      
      * | [--->] IT_KEY_TAB                     TYPE        /IWBEP/T_MGW_NAME_VALUE_PAIR
      
      * | [<-->] CS_KEY                         TYPE        DATA
      
      * | [!CX!] /IWBEP/CX_MGW_BUSI_EXCEPTION
      
      * +--------------------------------------------------------------------------------------</SIGNATURE>
      
        METHOD get_key.
      
      *--------------------------------------------------------------------*
      
      * Deklarationen
      
      *--------------------------------------------------------------------*
      
          DATA:
      
            lo_facade          TYPE REF TO /iwbep/cl_mgw_dp_facade,
      
            lo_context_entity  TYPE REF TO /iwbep/if_mgw_req_entity,
      
            lo_request_context TYPE REF TO /iwbep/cl_mgw_request,
      
            lt_headers         TYPE tihttpnvp,
      
            lr_entity          TYPE REF TO data,
      
            lr_source_keys     TYPE REF TO data.
      
      
      
          lo_facade           = CAST #( me->/iwbep/if_mgw_conv_srv_runtime~get_dp_facade( ) ).
      
          DATA(lo_model)      = lo_facade->/iwbep/if_mgw_dp_int_facade~get_model( ).
      
          DATA(lo_model_cast) = CAST /iwbep/cl_mgw_odata_model( lo_model ).
      
          DATA(lt_nav_path)   = io_tech_request_context->get_navigation_path( ).
      
      
      
          FIELD-SYMBOLS:
      
            <ls_ref_constraint> TYPE /iwbep/if_mgw_odata_re_assoc=>ty_s_mgw_odata_ref_constraint,
      
            <ls_source_keys>    TYPE data,
      
            <lv_value>          TYPE any,
      
            <lv_target>         TYPE any.
      
      
      
      
      
      
      
      *--------------------------------------------------------------------*
      
      * Auf Navigation prüfen
      
      *--------------------------------------------------------------------*
      
          IF lt_nav_path IS INITIAL.
      
            io_tech_request_context->get_converted_keys( IMPORTING es_key_values = cs_key ).
      
          ELSEIF lines( lt_nav_path ) EQ 1.
      
            DATA(lv_type) = |ZCL_ZTEST_MPC_EXT=>TS_{ io_tech_request_context->get_source_entity_type_name( ) CASE = UPPER }|.
      
            CREATE DATA lr_source_keys TYPE (lv_type).
      
            ASSIGN lr_source_keys->* TO <ls_source_keys>.
      
      
      
            io_tech_request_context->get_converted_source_keys(
      
              IMPORTING
      
                es_key_values = <ls_source_keys>
      
            ).
      
      
      
            " das ist etwas tricky.. sollte aber 99% der Fälle abdecken!
      
      
      
            " Entitätstyp der Zielentität bestimmen
      
            DATA(lo_entity_type) = lo_model_cast->/iwbep/if_mgw_odata_model~get_entity_type( io_tech_request_context->get_entity_type_name( ) ).
      
      
      
            " Navigationseigenschaften der Entität bestimmen
      
            DATA(lt_nav_props)   = lo_model_cast->/iwbep/if_mgw_odata_re_model~get_navigation_properties( ).
      
      
      
            " Anhand der NavEigenschaft die entsprechende Assoziation ermitteln
      
            DATA(lr_association) = lo_model_cast->get_association_by_id( lt_nav_props[ name = lt_nav_path[ lines( lt_nav_path ) ]-nav_prop ]-target_entity_id ).
      
            ASSIGN lr_association->* TO FIELD-SYMBOL(<ls_association>).
      
            CHECK <ls_association> IS ASSIGNED.
      
            DATA(lo_association)     = lo_model->get_association( <ls_association>-name ).
      
      
      
            " Referential Constraints der Assoziation bestimmen
      
            DATA(lt_ref_constraints) = lo_association->get_ref_constraints( ).
      
      
      
            " Assoziation auswerten
      
            DATA(lv_left_entity)       = lo_association->get_left_entity_type( )->get_name( ).
      
            DATA(lv_right_entity)      = lo_association->get_right_entity_type( )->get_name( ).
      
      
      
            " Keys anhand RefConstraint mappen
      
            LOOP AT lt_ref_constraints ASSIGNING <ls_ref_constraint>.
      
              UNASSIGN: <lv_target>, <lv_value>.
      
      
      
              IF lv_left_entity EQ io_tech_request_context->get_source_entity_type_name( ).
      
                ASSIGN COMPONENT <ls_ref_constraint>-source_property-name OF STRUCTURE <ls_source_keys> TO <lv_value>.
      
                ASSIGN COMPONENT <ls_ref_constraint>-target_property-name OF STRUCTURE cs_key           TO <lv_target>.
      
              ELSE.
      
                ASSIGN COMPONENT <ls_ref_constraint>-target_property-name OF STRUCTURE <ls_source_keys> TO <lv_value>.
      
                ASSIGN COMPONENT <ls_ref_constraint>-source_property-name OF STRUCTURE cs_key           TO <lv_target>.
      
              ENDIF.
      
      
      
              <lv_target> = <lv_value>.
      
            ENDLOOP.
      
      
      
      
      
            CHECK cs_key IS INITIAL.
      
      
      
            "--------------------------------------------------------------
      
            " Wir müssen hier die Source-Entität erst lesen, sonst kommen
      
            " wir hier leider nicht weiter.
      
            " --> kann passieren wenn wir nicht über einen $expand kommen,
      
            " und die linke Entität nicht vorne steht
      
            "--------------------------------------------------------------
      
            TRY .
      
      
      
                DATA(lv_entity_type_name) = io_tech_request_context->get_source_entity_type_name( ).
      
                DATA(lv_entity_set_name)  = io_tech_request_context->get_source_entity_set_name( ).
      
      
      
                me->/iwbep/if_mgw_core_srv_runtime~read_entity(
      
                  EXPORTING
      
                    iv_entity_name               = CONV #( lv_entity_type_name )
      
                    iv_source_name               = CONV #( lv_entity_type_name )
      
                    is_request_details           = VALUE #(
      
                                                      source_entity     = lv_entity_type_name
      
                                                      source_entity_set = lv_entity_set_name
      
                                                      target_entity     = lv_entity_type_name
      
                                                      target_entity_set = lv_entity_set_name
      
                                                      technical_request = VALUE #(
      
                                                        source_entity_type = lv_entity_type_name
      
                                                        target_entity_set  = lv_entity_set_name
      
                                                        key_tab            = io_tech_request_context->get_source_keys( )
      
                                                      )
      
                                                      key               = ''
      
                                                      key_tab           = it_key_tab
      
                                                   )
      
                  CHANGING
      
                    ct_headers                   = lt_headers
      
                    cr_entity                    = lr_entity
      
                ).
      
                ASSIGN lr_entity->* TO FIELD-SYMBOL(<ls_source_entity>).
      
                LOOP AT lt_ref_constraints ASSIGNING <ls_ref_constraint>.
      
                  ASSIGN COMPONENT <ls_ref_constraint>-source_property-name OF STRUCTURE <ls_source_entity> TO <lv_value>.
      
                  ASSIGN COMPONENT <ls_ref_constraint>-target_property-name OF STRUCTURE cs_key TO <lv_target>.
      
                  CHECK <lv_value> IS ASSIGNED AND <lv_target> IS ASSIGNED.
      
                  <lv_target> = <lv_value>.
      
                ENDLOOP.
      
              CATCH /iwbep/cx_mgw_busi_exception INTO DATA(lcx_busi).    "
      
                BREAK-POINT.
      
              CATCH /iwbep/cx_mgw_tech_exception INTO DATA(lcx_tech).    "
      
                BREAK-POINT.
      
            ENDTRY.
      
          ELSE.
      
            "--------------------------------------------------------------
      
            " Multi-Level Navigation wird nicht unterstützt.
      
            "--------------------------------------------------------------
      
            RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
      
              EXPORTING
      
                textid           = /iwbep/cx_mgw_busi_exception=>business_error
      
                message          = 'Multi-Level Navigation wird derzeit aufgrund der Datenintegrität nicht unterstützt!'
      
                http_status_code = /iwbep/cx_mgw_busi_exception=>gcs_http_status_codes-bad_request.
      
          ENDIF.
      
        ENDMETHOD.

      Hopefully you like it and won't hate me for doing this. ;-)

      I will extend this in future so that it can also resolve Nav-Pathes which are longer..

      Regards!

  • Jan 10 at 09:00 AM
    -1

    Try Using IO_TECH_REQUEST_CONTEXT->GET_NAVIGATION_PATH.

    Add comment
    10|10000 characters needed characters exceeded

  • Jan 10 at 05:02 PM

    After some further investigations I think this feature is not implemented in an convenient way in the SAP Gateway by default, and there is no good way to setup this in an generic way in the Data Provider Classes.

    Even when using an direct Mapping to the Data Source via SADL i get an CX_SADL_GW_CONTRACT_VIOLATION which tells me that an "Multi Level Navigation is not supported.".

    I think the main problem here is the Performance-Overload.

    By using an URI above ("/BusinessPartner('0123456789')/ToRelation(<insertKeyHere>)/ToRelationPartner") the Gateway won't trigger the Read-Operations for the first two segments of the Request by default.

    Using an generic way to read the missing segments with the NavigationPath (via io_tech_request_context->get_navigation_path) seems to be also not possible as im not able to call the "..._GET_ENTITY" or "..._GET_ENTITYSET" Methods from the DPC_EXT Classes with another technical context.

    I even tried to read the used Association (via the NavigationProperty) during runtime and get the referential constraints and do an generic mapping, but this will only work if the left Entity of the Association is the previous requested Segment in the URI. If the left Entity is the actual requested Entity I need to read the data from the right Entity as the Referential Constraint is then not the Key anymore. (hope this is clear, otherwise i will post an example with the Coding :) )

    In my Opinion the only way to achieve this is to hardcode every possible navigation in the corresponding GET_ENTITY or GET_ENTITYSET Methods, which is a pretty pretty ugly way to code.

    So sad that there is no API to call the first Segments. :-(

    If anyone has some more thoughts or inputs on this, i would really appreciate your answers!

    Add comment
    10|10000 characters needed characters exceeded

    • Hi Sascha,

      Definitely it will work with the URLs you have mentioned above. You can create a navigation from BusinessPartner to Relation and vice-versae as well. I suggested an alternate considering you had mentioned keys for both entities BusinessPartner and Relation in single URL.

      Best Regards.