Skip to Content

Multi-Level Navigation in OData-Service

Jan 10 at 07:32 AM


avatar image

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.


ToRelation (using Assocation ToRelation)

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

Key: RelationshipNo, RelationshipType, Partner1, Partner2, DateFrom


ToRelationPartner (using Assocation ToRelationPartner)


- ToRelation

SourceEntity: BusinessPartner

TargetEntity: Relation

Cardinality: 1 to 0..n


BusinessParter~ParterNo = Relation~Partner1

- ToRelationPartner

SourceEntity: BusinessPartner

TargetEntity: Relation

Cardinality: 1 to 1


BusinessPartner~PartnerNo = Relation~Partner2

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


->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:

--> 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:


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!


10 |10000 characters needed characters left characters exceeded
* Please Login or Register to Answer, Follow or Comment.

3 Answers

Best Answer
Andre Fischer
Jan 13 at 11:09 PM

Hi Sascha,

please check the implementation in the sample service GWSAMPLE_BASIC.

The following request


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


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

Best Regards,


Show 1 Share
10 |10000 characters needed characters left 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.


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

* <SIGNATURE>---------------------------------------------------------------------------------------+

* | Instance Private Method ZCL_ZBAR_BASIS_BP_DPC_EXT->GET_KEY

* +-------------------------------------------------------------------------------------------------+


* | [--->] IT_KEY_TAB                     TYPE        /IWBEP/T_MGW_NAME_VALUE_PAIR

* | [<-->] CS_KEY                         TYPE        DATA


* +--------------------------------------------------------------------------------------</SIGNATURE>

  METHOD get_key.


* Deklarationen



      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( ).


      <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>.



          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>.


          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>.


        <lv_target> = <lv_value>.


      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( ).



              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



              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>.


        CATCH /iwbep/cx_mgw_busi_exception INTO DATA(lcx_busi).    "


        CATCH /iwbep/cx_mgw_tech_exception INTO DATA(lcx_tech).    "





      " Multi-Level Navigation wird nicht unterstützt.


      RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception


          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.



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..


Ankit Maskara
Jan 10 at 09:00 AM


Show 1 Share
10 |10000 characters needed characters left characters exceeded

i already know about this method. This gives me the navigation path. But not the corresponding keys for my Relatiopartner in the example above.

Sascha Weidlich 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!

Show 3 Share
10 |10000 characters needed characters left characters exceeded

My understanding of your requirement is below -

You want to retrieve the details of a single business partner(as you specified the keys) and then get the details of a single relation(again you have put keys in URL) and then again want to retrieve the details of another business partner(by using Partner2 field of Relation Entity) in a single OData call. This is not directly possible. What you can do is try achieving same with a Function Import. Assuming you have the keys - PartnerNo(BusinessPartner Entity), Partner2(Relation Entity) create a (GET) Function Import which takes these as paramerets and returns the data in entity of type Business Partner.


Hi Ankit,

please see my answer to Andre. It works. Im now able to do the following:



Anyways. Thanks for your Help! :)



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.