Skip to Content
Author's profile photo Andre Fischer

How to handle navigation between CDS based entities and non-CDS based entities

Introduction

Already a while ago I stumbled accross the problem to build an OData service that leverages CDS views as a data source and that also contains entitysets based on ABAP Code based implementation.

The question was

a) Is it possible to implement a navigation between those different implementation types?

b) If yes, how to implement the same?

To keep the story short here are the results:

Scenario Source entity set based on Target entity set based on Result
1 RDS ABAP code based implementation Works
2 ABAP code based implementation RDS (Referenced data source) Does not work
3 ABAP code based implementation MDS (Mapped data source) Works

Though it is not possible to navigate from an entity set that is based on RDS there is a workaround by using the mapped data source (MDS) approach instead.

This approach has orignially been described in the following whitepaper (page 26, section 6.16 “Handling Odata navigation”)

http://www.sdn.sap.com/irj/scn/go/portal/prtroot/docs/library/uuid/00eaafa3-5f63-3110-72a7-f4d8044dbaba?QuickLink=index&overridelayout=true&59983513259546

In this document there is however one small important piece of Information missing.

The hint that alpha conversion for SALESORDER has to be used is missing. One has to pass the internal value (0500002030) instead of he external value (500002030) to io_query_options->add_condition_provider.

This can be achieved by calling io_tech_request_context->get_converted_source_keys( ) instead of get_source_keys( )in method SALESORDERITEMSE_GET_ENTITYSET.

Note that the method io_tech_request_context->get_converted_source_keys( ) doesn’t return name-value pairs, but you need to properly type parameter ES_KEY_VALUES, like you do in method SALESORDERSET_GET_ENTITY.

Service structure

So I created an OData service Z_SADL_NON_SADL_SRV with three entity sets.

Entity Set Based on CDS View DDIC table Navigation Property Service implementation
SEPM_I_BusinessPartner_E SEPM_I_BusinessPartner_E toSalesOrder RDS
SalesOrderSet —- SEPM_ISOE toItem ABAP Code based implementation
SalesOrderItemSet SEPM_I_SalesOrderItem_E SEPM_ISOIE MDS

This service allows for the following navigation

Business Partner to Sales Order –> …/SEPM_I_BusinessPartner_E(‘100000000’)/toSalesOrder

Sales Order to Sales Order Item –> SalesOrderSet(‘500002030’)/toItem

Service implementation

The Service Builder Project is shown here

The mapping for the Sales Order Items is done via Mapped Data Source Approach

The mapping was simply generated.

RDS to ABAP code based implementation

Model provider extension class

Here we have to add a Navigation property via ABAP Code in the DEFINE method of the MPC_EXT class since it is not possible to add a navigation property using the Service Builder in this case.

class ZCL_Z_SADL_NON_SADL_MPC_EXT definition
  public
  inheriting from ZCL_Z_SADL_NON_SADL_MPC
  create public .

public section.

  methods DEFINE
    redefinition .
protected section.
private section.
ENDCLASS.



CLASS ZCL_Z_SADL_NON_SADL_MPC_EXT IMPLEMENTATION.


  method DEFINE.

    super->define( ).

    data:
lo_annotation     type ref to /iwbep/if_mgw_odata_annotation,                   "#EC NEEDED
lo_entity_type    type ref to /iwbep/if_mgw_odata_entity_typ,                   "#EC NEEDED
lo_association    type ref to /iwbep/if_mgw_odata_assoc,                        "#EC NEEDED
lo_ref_constraint type ref to /iwbep/if_mgw_odata_ref_constr,                   "#EC NEEDED
lo_assoc_set      type ref to /iwbep/if_mgw_odata_assoc_set,                    "#EC NEEDED
lo_nav_property   type ref to /iwbep/if_mgw_odata_nav_prop.                     "#EC NEEDED

***********************************************************************************************************************************
*   ASSOCIATIONS
***********************************************************************************************************************************

 lo_association = model->create_association(
                            iv_association_name = 'AssocBuPa_SalesOrder' "#EC NOTEXT
                            iv_left_type        = 'SEPM_I_BusinessPartner_EType' "#EC NOTEXT
                            iv_right_type       = 'SalesOrder' "#EC NOTEXT
                            iv_right_card       = 'M' "#EC NOTEXT
                            iv_left_card        = '1'  "#EC NOTEXT
                            iv_def_assoc_set    = abap_false ). "#EC NOTEXT
* Referential constraint for association - AssocSalesOrder_Item
lo_ref_constraint = lo_association->create_ref_constraint( ).
lo_ref_constraint->add_property( iv_principal_property = 'BusinessPartner'   iv_dependent_property = 'Customer' ). "#EC NOTEXT
lo_assoc_set = model->create_association_set( iv_association_set_name  = 'AssocBuPa_SalesOrderSet'                         "#EC NOTEXT
                                              iv_left_entity_set_name  = 'SEPM_I_BusinessPartner_E'              "#EC NOTEXT
                                              iv_right_entity_set_name = 'SalesOrderSet'             "#EC NOTEXT
                                              iv_association_name      = 'AssocBuPa_SalesOrder' ).                                 "#EC NOTEXT


***********************************************************************************************************************************
*   NAVIGATION PROPERTIES
***********************************************************************************************************************************

* Navigation Properties for entity - SalesOrder
lo_entity_type = model->get_entity_type( iv_entity_name = 'SEPM_I_BusinessPartner_EType' ). "#EC NOTEXT
lo_nav_property = lo_entity_type->create_navigation_property( iv_property_name  = 'toSalesOrder' "#EC NOTEXT
                                                              iv_abap_fieldname = 'TOSALESORDER' "#EC NOTEXT
                                                              iv_association_name = 'AssocBuPa_SalesOrder' ). "#EC NOTEXT


  endmethod.
ENDCLASS.

Data provider extension class

In the target GET_ENTITYSET method of the SalesOrders we have to implement code that handles the retrieval of the Business Partner ID if the GET_ENTITYSET method is called via Navigation from the Entity set SEPM_I_BusinessPartner_E.

data: lv_businesspartner type sepm_ibupae-businesspartner.

    lt_nav_path = io_tech_request_context->get_navigation_path( ).

    read table lt_nav_path into ls_nav_path with key nav_prop = 'TOSALESORDER'.

    if sy-subrc = 0.

      call method io_tech_request_context->get_converted_source_keys
        importing
          es_key_values = ls_headerdata.

      lv_businesspartner = ls_headerdata-businesspartner.

      select * from sepm_i_salesorder_e
        into corresponding fields of table @et_entityset
        up to @lv_max_index rows
        where customer = @lv_businesspartner .

    else.
...

ABAP code based implementation to MDS

Data provider extension class

In this case we only have to redefine several methods in the data provider extension class of our Service Builder project.

Here we have to create two member variable in the private Definition section of the DPC_EXT class.

  private section.
    data mt_source_keys type /iwbep/t_mgw_tech_pairs .
    data mt_navigation_info type /iwbep/t_mgw_tech_navi .

These are used to pass Information between the methods:

salesorderitemse_get_entityset and
if_sadl_gw_query_control~set_query_options

The trick is basically the the get_entityset method in the DPC_EXT class is redefined and manipulates the navigation Information that is passed to the SADL Framework before it is calling the SADL implementation in the super class.

 

method salesorderitemse_get_entityset.

    data: lt_keys       type /iwbep/t_mgw_tech_pairs,
          ls_key        type /iwbep/s_mgw_tech_pair,
          ls_bp_id      type bapi_epm_bp_id,
          ls_headerdata type sepm_isoe.

    call method io_tech_request_context->get_converted_source_keys
      importing
        es_key_values = ls_headerdata.

    ls_key-name = 'SALESORDER'.
    ls_key-value = ls_headerdata-salesorder.

    append ls_key to mt_source_keys.

    mt_navigation_info = io_tech_request_context->get_navigation_path( ).

    try.
        call method super->salesorderitemse_get_entityset

Result

The following calls are working now

/sap/opu/odata/SAP/Z_SADL_NON_SADL_SRV/SalesOrderSet('500002030')/toItem?$format=json

 

{
  "d" : {
    "results" : [
      {
        "__metadata" : {
          "id" : "https://ldai4er9.wdf.sap.corp:44300/sap/opu/odata/SAP/Z_SADL_NON_SADL_SRV/SalesOrderItemSet(Salesorder='500002030',Salesorderitem='10')",
          "uri" : "https://ldai4er9.wdf.sap.corp:44300/sap/opu/odata/SAP/Z_SADL_NON_SADL_SRV/SalesOrderItemSet(Salesorder='500002030',Salesorderitem='10')",
          "type" : "Z_SADL_NON_SADL_SRV.SalesOrderItem"
        },
        "Salesorder" : "500002030",
        "Salesorderitem" : "10",
        "Product" : "HT-1000",
        "Transactioncurrency" : "EUR",
        "Grossamountintransaccurrency" : "1137.64",
        "Netamountintransactioncurrency" : "956.00",
        "Taxamountintransactioncurrency" : "181.64",
        "Productavailabilitystatus" : "",
        "Opportunityitem" : ""
      },
      {
        "__metadata" : {
          "id" : "https://ldai4er9.wdf.sap.corp:44300/sap/opu/odata/SAP/Z_SADL_NON_SADL_SRV/SalesOrderItemSet(Salesorder='500002030',Salesorderitem='20')",
          "uri" : "https://ldai4er9.wdf.sap.corp:44300/sap/opu/odata/SAP/Z_SADL_NON_SADL_SRV/SalesOrderItemSet(Salesorder='500002030',Salesorderitem='20')",
          "type" : "Z_SADL_NON_SADL_SRV.SalesOrderItem"
        },
        "Salesorder" : "500002030",
        "Salesorderitem" : "20",
        "Product" : "HT-1001",
        "Transactioncurrency" : "EUR",
        "Grossamountintransaccurrency" : "2972.62",
        "Netamountintransactioncurrency" : "2498.00",
        "Taxamountintransactioncurrency" : "474.62",
        "Productavailabilitystatus" : "",
        "Opportunityitem" : ""
      },

 

and

/sap/opu/odata/SAP/Z_SADL_NON_SADL_SRV/SalesOrderSet('500002030')/toItem?$format=json

 

{
  "d" : {
    "results" : [
      {
        "__metadata" : {
          "id" : "https://ldai4er9.wdf.sap.corp:44300/sap/opu/odata/SAP/Z_SADL_NON_SADL_SRV/SalesOrderItemSet(Salesorder='500002030',Salesorderitem='10')",
          "uri" : "https://ldai4er9.wdf.sap.corp:44300/sap/opu/odata/SAP/Z_SADL_NON_SADL_SRV/SalesOrderItemSet(Salesorder='500002030',Salesorderitem='10')",
          "type" : "Z_SADL_NON_SADL_SRV.SalesOrderItem"
        },
        "Salesorder" : "500002030",
        "Salesorderitem" : "10",
        "Product" : "HT-1000",
        "Transactioncurrency" : "EUR",
        "Grossamountintransaccurrency" : "1137.64",
        "Netamountintransactioncurrency" : "956.00",
        "Taxamountintransactioncurrency" : "181.64",
        "Productavailabilitystatus" : "",
        "Opportunityitem" : ""
      },
      {
        "__metadata" : {
          "id" : "https://ldai4er9.wdf.sap.corp:44300/sap/opu/odata/SAP/Z_SADL_NON_SADL_SRV/SalesOrderItemSet(Salesorder='500002030',Salesorderitem='20')",
          "uri" : "https://ldai4er9.wdf.sap.corp:44300/sap/opu/odata/SAP/Z_SADL_NON_SADL_SRV/SalesOrderItemSet(Salesorder='500002030',Salesorderitem='20')",
          "type" : "Z_SADL_NON_SADL_SRV.SalesOrderItem"
        },
        "Salesorder" : "500002030",
        "Salesorderitem" : "20",
        "Product" : "HT-1001",
        "Transactioncurrency" : "EUR",
        "Grossamountintransaccurrency" : "2972.62",
        "Netamountintransactioncurrency" : "2498.00",
        "Taxamountintransactioncurrency" : "474.62",
        "Productavailabilitystatus" : "",
        "Opportunityitem" : ""
      },
      {

 

Complete DPC_EXT code

class zcl_z_sadl_non_sadl_dpc_ext definition
  public
  inheriting from zcl_z_sadl_non_sadl_dpc
  create public .

  public section.

    methods if_sadl_gw_query_control~set_query_options
        redefinition .
  protected section.

    methods salesorderitemse_get_entityset
        redefinition .
    methods salesorderset_get_entityset
        redefinition .
    methods salesorderset_get_entity
        redefinition .
  private section.

    data mt_source_keys type /iwbep/t_mgw_tech_pairs .
    data mt_navigation_info type /iwbep/t_mgw_tech_navi .
endclass.



class zcl_z_sadl_non_sadl_dpc_ext implementation.


  method if_sadl_gw_query_control~set_query_options.

    case iv_entity_set.

      when 'SalesOrderItemSet'.

        if mt_navigation_info is not initial.
          data(ls_nav_step) = mt_navigation_info[ 1 ].
          " Is this the navigation we have to handle (SalesOrder -> SalesOrderItems)
          if ls_nav_step-source_entity_type = 'SalesOrder'
          and ls_nav_step-nav_prop = 'TOITEM'.
            " Make SADL ignore navigation
            io_query_options->remove_navigation_info( ).
            " Define condition based on source entity key
            data(lo_cond_factory) =
                 cl_sadl_cond_prov_factory_pub=>create_basic_condition_factory( ).
            io_query_options->add_condition_provider(
            lo_cond_factory->equals(
            name = 'SALESORDER' " ABAP field name of GW property
            value = mt_source_keys[ name = 'SALESORDER' ]-value ) ).
          endif.
        endif.

      when others.

        try.
            call method super->if_sadl_gw_query_control~set_query_options
              exporting
                iv_entity_set    = iv_entity_set
                io_query_options = io_query_options.
          catch /iwbep/cx_mgw_busi_exception .
          catch /iwbep/cx_mgw_tech_exception .
        endtry.

    endcase.

  endmethod.


  method salesorderitemse_get_entityset.

    data: lt_keys       type /iwbep/t_mgw_tech_pairs,
          ls_key        type /iwbep/s_mgw_tech_pair,
          ls_bp_id      type bapi_epm_bp_id,
          ls_headerdata type sepm_isoe.

    call method io_tech_request_context->get_converted_source_keys
      importing
        es_key_values = ls_headerdata.

    ls_key-name = 'SALESORDER'.
    ls_key-value = ls_headerdata-salesorder.

    append ls_key to mt_source_keys.

    mt_navigation_info = io_tech_request_context->get_navigation_path( ).

    try.
        call method super->salesorderitemse_get_entityset
          exporting
            iv_entity_name           = iv_entity_name
            iv_entity_set_name       = iv_entity_set_name
            iv_source_name           = iv_source_name
            it_filter_select_options = it_filter_select_options
            is_paging                = is_paging
            it_key_tab               = it_key_tab
            it_navigation_path       = it_navigation_path
            it_order                 = it_order
            iv_filter_string         = iv_filter_string
            iv_search_string         = iv_search_string
            io_tech_request_context  = io_tech_request_context
          importing
            et_entityset             = et_entityset
            es_response_context      = es_response_context.
      catch /iwbep/cx_mgw_busi_exception .
      catch /iwbep/cx_mgw_tech_exception .
    endtry.

  endmethod.

  method salesorderset_get_entity.

    data: lt_keys       type /iwbep/t_mgw_tech_pairs,
          ls_key        type /iwbep/s_mgw_tech_pair,
          ls_headerdata type sepm_isoe.

    call method io_tech_request_context->get_converted_keys
      importing
        es_key_values = ls_headerdata.

    select single *
      into corresponding fields of @er_entity
      from sepm_i_salesorder_e
      where salesorder = @ls_headerdata-salesorder.

  endmethod.

  method salesorderset_get_entityset.

    data: lt_nav_path   type /iwbep/t_mgw_tech_navi,
          ls_nav_path   type /iwbep/s_mgw_tech_navi,
          ls_bp_id      type bapi_epm_bp_id,
          ls_headerdata type sepm_ibupae.

    data: lv_osql_where_clause type string,
          lv_top               type i,
          lv_skip              type i,
          lv_max_index         type i,
          n                    type i.

    data: lv_businesspartner type sepm_ibupae-businesspartner.

    lt_nav_path = io_tech_request_context->get_navigation_path( ).

    read table lt_nav_path into ls_nav_path with key nav_prop = 'TOSALESORDER'.

    if sy-subrc = 0.

      call method io_tech_request_context->get_converted_source_keys
        importing
          es_key_values = ls_headerdata.

      lv_businesspartner = ls_headerdata-businesspartner.

      select * from sepm_i_salesorder_e
        into corresponding fields of table @et_entityset
        up to @lv_max_index rows
        where customer = @lv_businesspartner .

    else.

      lv_osql_where_clause = io_tech_request_context->get_osql_where_clause( ).

      select * from sepm_i_salesorder_e
        into corresponding fields of table @et_entityset
        up to @lv_max_index rows
        where (lv_osql_where_clause).

    endif.

  endmethod.
endclass.

Assigned Tags

      5 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Nabheet Madan
      Nabheet Madan

      Thanks Andre Fischer for sharing this, much needed  I believe.  I remember creating service using  ABAP code based implementation now we can use the hybrid approach with navigation support between CDS and ABAP based. Great! Thanks

      Author's profile photo Pawan Kalyan K
      Pawan Kalyan K

      Thanks Andre Fischer  for the wonderful blog ...!!!

      What happens if we use a CDS view which is BOPF Enabled and Transactional Processing Enabled as an RDS .

      Can we use that? If Yes, do we need to care only about Navigations and handle logic like the way you mentioned?

       

      Author's profile photo Andre Fischer
      Andre Fischer
      Blog Post Author

      I am not aware of any restrictions.

      But I have never tried it out.

      Regards,

      Andre

       

      Author's profile photo Lokeswar Reddy Byni
      Lokeswar Reddy Byni

      Hi Andre,

      I tried this approach and im getting the error as ‘Navigation property’ is invalid  – when i execute the post service.

      Please suggest...

       

      Thanks,

      Lokeswar.

       

       

      Author's profile photo Abhijeet Kankani
      Abhijeet Kankani

      Hi Andre,

       

      It is a really nice blog.

      We have a similar kind of requirement where the Source entity set based on RDS and the Target entity set based on ''ABAP code-based implementation' but in CDS view we are using aggregation (@DefaultAggregation: #SUM ) so there is no semantics key but one technical key is getting generated.

       

      Do you have any idea if we can create navigation in the above case as the technical key is always ID.

       

      Regards,

      Abhijeet Kankani