Skip to Content
Technical Articles
Author's profile photo Andre Fischer

OData V4 code based implementation I (basic interface, read access)

Table of contents

This blog is part of blog series about OData V4 code based development

OData V4 code based implementation – Overview

OData V4 code based implementation I (basic interface, read access)

OData V4 code based implementation I (basic interface, create&update)

 

Source code

 

The source code can be found on GitHub.

Data provider class – zcl_e2e001_odata_v4_so_data

Model provider class – zcl_e2e001_odata_v4_so_model

Exception class – zcx_e2e001_odata_v4_so

Interface – zif_e2e001_odata_v4_so_types

Consumption view – sales order – ze2e001_c_salesorder

Consumption view sales order items – ze2e001_c_salesorderitem

Interface view – ze2e001_i_salesorderitem_e

Consumption view – ze2e001_c_salesorder

 

Introduction

In this first blog about OData V4 code based implementation I want to show how to build a simple service that shows sales order header data alongside with its items.

The service implementation will leverage two CDS consumption views ZE2E001_C_SalesOrder and ZE2E001_C_SalesOrderItem that read their data from the CDS interface views SEPM_I_SalesOrder_E and SEPM_I_SalesOrderItem_E respectively.

Please note:

By leveraging CDS views you will be able to reuse the data modelling part to a large extent in the new ABAP programming model once this supports OData V4 even if you are currently using AS ABAP 750. In addition your service will support most query options out of the box by using the code samples shown in this blog.

In addition to the data provider class and the model provider class which are mandatory for an OData V4 service we will in create three additional repository objects for convenience (an interface) and as a best practice (an error and an message class).

The interface is used to define types and constants that are used in both, the data provider class as well as in the model provider class. Examples are the types of the two CDS consumption views mentioned above or the ABAP internal and external names of our entity types, entity sets, navigation property, etc.

 

How to test the service

This sample service has been implemented for your convenience in the new demo system ES5. System details can be found in my following blog.New SAP Gateway Demo System available.

The $metadata document of the service can than be called via the following URL

https://sapes5.sapdevcenter.com/sap/opu/odata4/sap/ze2e001/default/sap/ze2e001_salesorder/0001/$metadata?sap-statistics=true

Using the implementation of the basic interface methods our service already supports the following requests:

Read a single sales order

https://sapes5.sapdevcenter.com/sap/opu/odata4/sap/ze2e001/default/sap/ze2e001_salesorder/0001/SalesOrder(‘500000000’)?sap-ds-debug=true

Get the first three sales orders

https://sapes5.sapdevcenter.com/sap/opu/odata4/sap/ze2e001/default/sap/ze2e001_salesorder/0001/SalesOrder?$top=3&sap-ds-debug=true

Navigate from the sales order head to the items and filter the result set for items with a grossamount larger than 1100$.

https://sapes5.sapdevcenter.com/sap/opu/odata4/sap/ze2e001/default/sap/ze2e001_salesorder/0001/SalesOrder(‘500000000’)/_Item?$filter=Grossamountintransaccurrency ge 1100 and Transactioncurrency eq ‘USD’&sap-ds-debug=true

Read a sales order header and expand the items therby filtering on the expanded items.

https://sapes5.sapdevcenter.com/sap/opu/odata4/sap/ze2e001/default/sap/ze2e001_salesorder/0001/SalesOrder(‘500000000’)?$expand=_Item($filter=Grossamountintransaccurrency ge 1100 and Transactioncurrency eq ‘USD’)&sap-ds-debug=true

Repository objects

(listed in the order of implementation)

In the end our ABAP project will contain the following 7 repository objects:

 # name role Details:
 1 ZE2E001_C_SalesOrder CDS consumption view – sales order reads data from SEPM_I_SALESORDER_E
 2 ZE2E001_C_SalesOrderItem CDS consumption view – sales order item reads data from SEPM_I_SALESORDERITEM_E
 3 zif_e2e001_odata_v4_so_types Interface  is used by the data provider class as well as the model provider class
4 zcm_e2e001odatav4_so Message class contains 2 messages
5 zcx_e2e001_odata_v4_so Exception class inherits from:
/iwbep/cx_gateway
 6 zcl_e2e001_odata_v4_so_model Model provider class inherits from:
/iwbep/cl_v4_abs_model_prov
 7 zcl_e2e001_odata_v4_so_data Data provider class inherits from:
/iwbep/cl_v4_abs_data_provider

And our project explorer structure in the ABAP Developmen Tools in Eclipse will look like follows:

 

CDS consumption views ZE2E001_C_SalesOrder and ZE2E001_C_SalesOrderItem

We will start to create two CDS consumption views on top of two existing CDS Interface views that are delivered by SAP as part of the EPM demo data model.

@AbapCatalog.sqlViewName: 'ZE2E001CSO'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'OData V4 Demo Service - SalesOrder Root'
define view ZE2E001_C_SalesOrder
  as select from SEPM_I_SalesOrder_E

  association [1..*] to ZE2E001_C_SALESORDERITEM as _Item on _Item.SalesOrder = $projection.SalesOrder
  association [0..*] to SEPM_I_SalesOrderText_E  as _Text on $projection.SalesOrder = _Text.SalesOrder


{
      //SEPM_I_SalesOrder_E
  key SalesOrder,
      CreatedByUser,
      CreationDateTime,
      LastChangedByUser,
      LastChangedDateTime,
      IsCreatedByBusinessPartner,
      IsLastChangedByBusinessPartner,
      Customer,
      CustomerContact,
      TransactionCurrency,
      @Semantics.amount.currencyCode: 'TransactionCurrency'
      GrossAmountInTransacCurrency,
      @Semantics.amount.currencyCode: 'TransactionCurrency'
      NetAmountInTransactionCurrency,
      @Semantics.amount.currencyCode: 'TransactionCurrency'
      TaxAmountInTransactionCurrency,
      SalesOrderLifeCycleStatus,
      SalesOrderBillingStatus,
      SalesOrderDeliveryStatus,
      SalesOrderOverallStatus,
      Opportunity,
      SalesOrderPaymentMethod,
      SalesOrderPaymentTerms,
      BillToParty,
      BillToPartyRole,
      ShipToParty,
      ShipToPartyRole,
      /* Associations */
      _Item,
      _Text

}

 

@AbapCatalog.sqlViewName: 'ZE2E001CSOI'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'OData V4 Demo Service - SalesOrder Items'
define view Ze2e001_C_Salesorderitem 
 as select from SEPM_I_SalesOrderItem_E
  association [0..*] to SEPM_I_SalesOrderItemText_E as _Text on  $projection.SalesOrder     = _Text.SalesOrder
                                                             and $projection.SalesOrderItem = _Text.SalesOrderItem
{
      //SEPM_I_SalesOrderItem_E
  key SalesOrder,
  key SalesOrderItem,
      Product,
      TransactionCurrency,
      @Semantics.amount.currencyCode: 'TransactionCurrency'
      GrossAmountInTransacCurrency,
      @Semantics.amount.currencyCode: 'TransactionCurrency'
      NetAmountInTransactionCurrency,
      @Semantics.amount.currencyCode: 'TransactionCurrency'
      TaxAmountInTransactionCurrency,
      ProductAvailabilityStatus,
      OpportunityItem,
      /* Associations */
      _Text
} 
 
 

Please note that you have to activate both views together (Ctrl+Shift+F3). Afterwards you can select the sales order view and use (F8) to get a preview of the data.

Interface – zif_e2e001_odata_v4_so_types

In the second step we create an interface zif_e2e001_odata_v4_so_types that will be used to store data types and constants that will be used in both, the model provider and the data provider class.

It can also be used for other service implementations that reside in the same service group and that might want to leverage the information being defined in the interface to implement cross service references.

interface zif_e2e001_odata_v4_so_types
  public .
  types:
    begin of gty_cds_views,
      salesorderitem type ze2e001_c_salesorderitem,
      salesorder     type ze2e001_c_salesorder,
    end of gty_cds_views.

  types: begin of gty_s_so_soi .
      include type gty_cds_views-salesorder.
  types:
    _item type standard table of gty_cds_views-salesorderitem with default key,
    end of gty_s_so_soi .

  types:
    begin of gt_key_range,
      salesorder   type range of gty_cds_views-salesorder-salesorder,
      itemposition type range of gty_cds_views-salesorderitem-salesorderitem,
    end of gt_key_range.

  constants:

    begin of gcs_cds_view_names,
      salesorderitem type /iwbep/if_v4_med_element=>ty_e_med_internal_name value 'SEPM_ODATA_C_SALESORDERITEM',
      salesorder     type /iwbep/if_v4_med_element=>ty_e_med_internal_name value 'SEPM_ODATA_C_SALESORDER',
    end of gcs_cds_view_names,

    begin of gcs_entity_type_names,
      begin of internal,
        salesorderitem type /iwbep/if_v4_med_element=>ty_e_med_internal_name value 'SEPM_ODATA_C_SALESORDERITEM',
        salesorder     type /iwbep/if_v4_med_element=>ty_e_med_internal_name value 'SEPM_ODATA_C_SALESORDER',
      end of internal,
      begin of edm,
        salesorderitem type /iwbep/if_v4_med_element=>ty_e_med_edm_name value 'SalesOrderItemType',
        salesorder     type /iwbep/if_v4_med_element=>ty_e_med_edm_name value 'SalesOrderType',
      end of edm,
    end of gcs_entity_type_names,

    begin of gcs_entity_set_names,
      begin of internal,
        salesorderitem type /iwbep/if_v4_med_element=>ty_e_med_internal_name value 'SEPM_ODATA_C_SALESORDERITEM',
        salesorder     type /iwbep/if_v4_med_element=>ty_e_med_internal_name value 'SEPM_ODATA_C_SALESORDER',
      end of internal,
      begin of edm,
        salesorderitem type /iwbep/if_v4_med_element=>ty_e_med_edm_name value 'SalesOrderItem',
        salesorder     type /iwbep/if_v4_med_element=>ty_e_med_edm_name value 'SalesOrder',
      end of edm,
    end of gcs_entity_set_names ,

    begin of gcs_nav_prop_names,
      begin of internal,
        salesorder_to_items type /iwbep/if_v4_med_element=>ty_e_med_internal_name value '_ITEM',
      end of internal,
      begin of edm,
        salesorder_to_items type /iwbep/if_v4_med_element=>ty_e_med_edm_name value '_Item',
      end of edm,
    end of gcs_nav_prop_names.

endinterface.

Message class

The message class contains three messages that are used in the exception class to raise error messages specific to your OData service.

 Message Number Short Text  Self Explanatory
050  Entity &1 not found in entiy set &2  X
051  filter, top or navigation must be used to access entity set &1  X
052  Entity key &1 does not match key in payload  X

The message class will look like follows in ADT.

Exception class

It is a good practice to use your own exception classes. The class inherits from the basic exception class provided by the SAP Gateway framework and contains exceptions that are raised if the following errors occur:

  • the application tries to read header data of a sales order that does not exist
  • the application tries to read a list of sales orders or sales order items without providing any query options to avoid a full table scan

Please note:

SAP Fiori applications for example by default use query options such as $top and $skip. A single read on a sales order will also use the correct key that has been retrieved beforehand.

class zcx_e2e001_odata_v4_so definition
  public
  inheriting from /iwbep/cx_gateway
  final
  create public .

  public section.

    constants:
      begin of entity_not_found,
        msgid type symsgid value 'ZCM_E2E001ODATAV4_SO',
        msgno type symsgno value '050',
        attr1 type scx_attrname value 'ENTITY_KEY',
        attr2 type scx_attrname value 'EDM_ENTITY_SET_NAME',
        attr3 type scx_attrname value '',
        attr4 type scx_attrname value '',
      end of entity_not_found.
    constants:
      begin of use_filter_top_or_nav,
        msgid type symsgid value 'ZCM_E2E001ODATAV4_SO',
        msgno type symsgno value '051',
        attr1 type scx_attrname value 'EDM_ENTITY_SET_NAME',
        attr2 type scx_attrname value '',
        attr3 type scx_attrname value '',
        attr4 type scx_attrname value '',
      end of use_filter_top_or_nav .
    constants:
      begin of entity_keys_do_not_match,
        msgid type symsgid value 'ZCM_E2E001ODATAV4_SO',
        msgno type symsgno value '052',
        attr1 type scx_attrname value 'EDM_ENTITY_KEY',
        attr2 type scx_attrname value '',
        attr3 type scx_attrname value '',
        attr4 type scx_attrname value '',
      end of entity_keys_do_not_match .

    data: entity_set_name     type /iwbep/if_v4_med_element=>ty_e_med_internal_name,
          edm_entity_set_name type /iwbep/if_v4_med_element=>ty_e_med_edm_name,
          user_name           type syuname,
          entity_key          type string read-only.



    methods constructor
      importing
        !textid              like if_t100_message=>t100key optional
        !previous            like previous optional
        !exception_category  type ty_exception_category default gcs_excep_categories-provider
        !http_status_code    type ty_http_status_code default gcs_http_status_codes-sv_internal_server_error
        !is_for_user         type abap_bool default abap_true
        !message_container   type ref to /iwbep/if_v4_message_container optional
        !sap_note_id         type ty_sap_note_id optional
        !edm_entity_set_name type /iwbep/if_v4_med_element=>ty_e_med_edm_name optional
        !entity_set_name     type /iwbep/if_v4_med_element=>ty_e_med_internal_name optional
        !user_name           type syuname optional
        !entity_key          type string optional.
  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.



CLASS ZCX_E2E001_ODATA_V4_SO IMPLEMENTATION.


METHOD constructor ##ADT_SUPPRESS_GENERATION.
    CALL METHOD super->constructor
      EXPORTING
        previous           = previous
        exception_category = exception_category
        http_status_code   = http_status_code
        is_for_user        = is_for_user
        message_container  = message_container
        sap_note_id        = sap_note_id.

    me->entity_key = entity_key.
    me->user_name = user_name.
    me->entity_set_name = entity_set_name .
    me->edm_entity_set_name = edm_entity_set_name.

    CLEAR me->textid.
    IF textid IS INITIAL.
      if_t100_message~t100key = if_t100_message=>default_textid.
    ELSE.
      if_t100_message~t100key = textid.
    ENDIF.
  ENDMETHOD.
ENDCLASS.

Model provider class – zcl_e2e001_odata_v4_so_model – coding explained

Now that we have worked on the prerequisites we can finally start to implement our model provider class. As in V2 the model provider class contains a DEFINE method that is amongst others responsible to create the definition of our entity types and entity sets.

The define method will call two entity type specific methods define_salesorder and define_salesorderitem that create the entity types and entity sets for sales order header and items.

  method /iwbep/if_v4_mp_basic~define.
    define_salesorder( io_model ).
    define_salesorderitem( io_model ).
  endmethod.

The most important method call is to call the method create_entity_type_by_struct which works similar to the DDIC import in SEGW in V2.

Please note that we make use of our interface here since the ABAP internal name of the entity type is retrieved via the constant gcs_entity_type_names-internal-salesorder and the DDIC type from the type gty_cds_views-salesorder.

method define_salesorder.
    data: lt_primitive_properties type /iwbep/if_v4_med_element=>ty_t_med_prim_property,
          lo_entity_set           type ref to /iwbep/if_v4_med_entity_set,
          lo_nav_prop             type ref to /iwbep/if_v4_med_nav_prop,
          lo_entity_type          type ref to /iwbep/if_v4_med_entity_type,
          lv_referenced_cds_view  type gty_cds_views-salesorder  . 
          " As internal ABAP name we use the name of the CDS view


    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
    "   Create entity type
    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
    lo_entity_type = io_model->create_entity_type_by_struct(
                      exporting
                        iv_entity_type_name          = gcs_entity_type_names-internal-salesorder
                        is_structure                 = lv_referenced_cds_view
                        iv_add_conv_to_prim_props    = abap_true
                        iv_add_f4_help_to_prim_props = abap_true
                        iv_gen_prim_props            = abap_true ).

Here we also set the key field of our entity type.

    lo_primitive_property = lo_entity_type->get_primitive_property( 'SALESORDER' ).
    lo_primitive_property->set_is_key( ).

And we create a navigation property in our entity type that points to the sales order items. Also here we use our interface to retrieve the ABAP internal and the external name of the navigation property gcs_nav_prop_names-internal-salesorder_to_items and  gcs_nav_prop_names-edm-salesorder_to_items as well as the ABAP internal name of the target entity type gcs_entity_type_names-internal-salesorderitem.

lo_nav_prop = lo_entity_type->create_navigation_property( gcs_nav_prop_names-internal-salesorder_to_items ).
lo_nav_prop->set_edm_name( gcs_nav_prop_names-edm-salesorder_to_items ).

lo_nav_prop->set_target_entity_type_name( gcs_entity_type_names-internal-salesorderitem ).
lo_nav_prop->set_target_multiplicity( /iwbep/if_v4_med_element=>gcs_med_nav_multiplicity-to_many_optional ).
lo_nav_prop->set_on_delete_action( /iwbep/if_v4_med_element=>gcs_med_on_delete_action-none ).

 

Data Provider class

The data provider class contains the generic implementation of the basic interface methods for read access. These are the following methods.

  • /iwbep/if_v4_dp_basic~read_entity_list
  • /iwbep/if_v4_dp_basic~read_entity
  • /iwbep/if_v4_dp_basic~read_ref_target_key_data_list

that  have to be redefined.

In addition it contains entity set specific impelmentations for both entity sets Salesorder and Salesorderitems. These are the following methods:

  • read_entity_salesorder
  • read_entity_salesorderitem
  • read_list_salesorder
  • read_list_salesorderitem
  • read_ref_key_list_salesorder

I will explain the functionality of these methods in the following. The complete code is shown in the following section.

/iwbep/if_v4_dp_basic~read_entity_list.

We start with the method /iwbep/if_v4_dp_basic~read_entity_list.which is for example called if a client accesses an entity set with a Get request, for example GET ….<service root>/Salesorder.

We first have to retrieve the todo list via the io_request object.

io_request->get_todos( importing es_todo_list = ls_todo_list ).

With this todo list we can check whether our service implementation has to retrieve parameters such as $top or $skip.

    " $skip / $top handling
    if ls_todo_list-process-skip = abap_true.
      ls_done_list-skip = abap_true.
      io_request->get_skip( importing ev_skip = lv_skip ).
    endif.
    if ls_todo_list-process-top = abap_true.
      ls_done_list-top = abap_true.
      io_request->get_top( importing ev_top = lv_top ).
    endif.

The implementation /iwbep/if_v4_dp_basic~read_entity_list contains generic coding to retrieve the following query options:

  • $orderby
  • $select
  • $filter
  • $skip
  • $top

for all entity sets, so that they can be passed as parameters to the entity set specific private methods.read_list_salesorder and read_list_salesorderitem,

Please note that the name of the entity set is retrieved via the method io_request->get_entity_set and checked against the constant gcs_entity_set_names-internal-salesorder that has been defined in our interface.

    io_request->get_entity_set( importing ev_entity_set_name = lv_entityset_name ).

    case lv_entityset_name.

      when gcs_entity_set_names-internal-salesorder.

        read_list_salesorder(
          exporting
            io_request        = io_request
            io_response       = io_response
            iv_orderby_string = lv_orderby_string
            iv_select_string  = lv_select_string
            iv_where_clause   = lv_where_clause
            iv_skip           = lv_skip
            iv_top            = lv_top
            is_done_list      = ls_done_list ).

 

read_list_salesorder

The entity type specifc methods read_list_salesorder and read_list_salesorderitem both start with a definition of entity type specific data types that will hold the data being returned and (if being provided) the list of key fields retrieved via navigation.

lt_key_range_salesorder type zif_e2e001_odata_v4_so_types=>gt_key_range-salesorder,
ls_key_range_salesorder type line of zif_e2e001_odata_v4_so_types=>gt_key_range-salesorder,
lt_salesorder type standard table of gty_cds_views-salesorder,
lt_key_salesorder  type standard table of gty_cds_views-salesorder.

"generic data types
data: ls_todo_list type /iwbep/if_v4_requ_basic_list=>ty_s_todo_list,
      ls_done_list type /iwbep/if_v4_requ_basic_list=>ty_s_todo_process_list,
      lv_count     type i,
      lv_max_index type i.

The code also uses our exception class since an exception will be raised if  the client would try to send a Get request   GET…/Salesorder without limiting the result set using either $filter, navigation or $top.

    if  ls_todo_list-process-filter = abap_false
    and ls_todo_list-process-key_data = abap_false
    and iv_top = 0.
      raise exception type zcx_e2e001_odata_v4_so
        exporting
          textid              = zcx_e2e001_odata_v4_so=>use_filter_top_or_nav
          http_status_code    = zcx_e2e001_odata_v4_so=>gcs_http_status_codes-bad_request
          edm_entity_set_name = gcs_entity_set_names-edm-salesorder.
    endif.

In the end we are able to run a generic OpenSQL statement against our CDS view which supports most query options out of the box.

An exception is for example $skip since offset is only supported in OpenSQL as of AS ABAP 751. If you are using AS ABAP 750  you have to work with first selecting  lv_max_index = iv_top + iv_skip table entries and later delete those from the response that have to be skipped (see complete coding at the end of this section)

Please note that the V4 API uses the method io_response->set_busi_data to return the business data to the SAP Gateway framework.

      "OFFSET is only supported as of NW751
      select (iv_select_string) from ze2e001_c_salesorder
      where (iv_where_clause)
      and   salesorder in @lt_key_range_salesorder
      order by (iv_orderby_string)
      into corresponding fields of table @lt_salesorder
      up to @iv_top rows
      offset @iv_skip.

      io_response->set_busi_data( it_busi_data = lt_salesorder ).

 

/iwbep/if_v4_dp_basic~read_entity

We continue with the method /iwbep/if_v4_dp_basic~read_entity which is for example called if a client accesses a single entity with a Get request, for example
GET ….<service root>/Salesorder(‘50000000’).

This time we first retrieve the name of the entity set that has been accessed using the method io_request->get_entity_set.

    io_request->get_entity_set( importing ev_entity_set_name = lv_entityset_name ).

    case lv_entityset_name.

      when gcs_entity_set_names-internal-salesorder.
        read_entity_salesorder(
          exporting
            io_request  = io_request
            io_response = io_response ).

read_entiy_salesorder and  read_entiy_salesorderitem

Here we have to retrieve the todo list via the io_request object. This todo list will contain different flags than the todo list used by the read_entity_list method. We can for example retrieve the key from the incoming Get request in the ABAP internal representation. We then can simply read the data via a select single statement from the CDS view. If data has been found it is returned to the framework via the method io_response->set_busi_data. If no table entry is found we raise an error message.using our exception class.

Please note that our coding is very generic so that  it can easily be adapted to the CDS viewyou will use.

 

  method read_entity_salesorder.

    "entity type specific data types
    data: ls_salesorder         type gty_cds_views-salesorder,
          ls_key_salesorder     type gty_cds_views-salesorder,
          lv_salesorder_key_edm type string,
          lv_helper_int         type i.
    "generic data types
    data: ls_todo_list type /iwbep/if_v4_requ_basic_read=>ty_s_todo_list,
          ls_done_list type /iwbep/if_v4_requ_basic_read=>ty_s_todo_process_list.

    io_request->get_todos( importing es_todo_list = ls_todo_list ).

    " read the key data
    io_request->get_key_data( importing es_key_data = ls_key_salesorder ).
    ls_done_list-key_data = abap_true.

    select single * from ze2e001_c_salesorder
    into corresponding fields of @ls_salesorder
    where salesorder = @ls_key_salesorder-salesorder.

    if ls_salesorder is not initial.
      io_response->set_busi_data( is_busi_data = ls_salesorder ).
    else.
      "Move data first to an integer to remove leading zeros from the response
      lv_salesorder_key_edm = lv_helper_int = ls_key_salesorder-salesorder.

      raise exception type zcx_e2e001_odata_v4_so
        exporting
          textid              = zcx_e2e001_odata_v4_so=>entity_not_found
          http_status_code    = zcx_e2e001_odata_v4_so=>gcs_http_status_codes-not_found
          edm_entity_set_name = gcs_entity_set_names-edm-salesorder
          entity_key          = lv_salesorder_key_edm.

    endif.

    " Report list of request options handled by application
    io_response->set_is_done( ls_done_list ).
  endmethod.

/iwbep/if_v4_dp_basic~read_ref_target_key_data_list

This method is called by the framework to determine the list of key fields in case navigation is used.

  method /iwbep/if_v4_dp_basic~read_ref_target_key_data_list.

    data: lv_source_entity_name type /iwbep/if_v4_med_element=>ty_e_med_internal_name.


    io_request->get_source_entity_type( importing ev_source_entity_type_name = lv_source_entity_name ).

    case lv_source_entity_name.

      when gcs_entity_type_names-internal-salesorder.
        read_ref_key_list_salesorder(
           exporting
            io_request  = io_request
            io_response = io_response ).

      when others.
        super->/iwbep/if_v4_dp_basic~read_ref_target_key_data_list(
          exporting
            io_request  = io_request
            io_response = io_response ).

    endcase.

  endmethod.

Here we check for the source entity type io_request->get_source_entity_type and call an entity type specific method read_ref_key_list_salesorder.

read_ref_key_list_salesorder

This method retrieves the ABAP internal value of the key field of our source entity (here 0500000000), if a requests such as

SalesOrder(‘500000000’)/_Item or

SalesOrder(‘500000000’)?$expand=_Item 

have been called.

Please  note that we are following the naming convention that uses the same name for the navigation property that is used as an association in the CDS view.

After having checked the ABAP internal name of the navigation property (here: _ITEM) that is defined in the interface we retrieve the key fields (salesorder and salesorderitem) from the CDS view ze2e001_c_salesorderitem where the key field salesorder is equal to the source key field (here 0500000000).

The key data is sent back to the framework which provides it to the read_entity_list method.

  method read_ref_key_list_salesorder.

    "entity type specific data types
    data: ls_salesorder_key_data     type  gty_cds_views-salesorder,
          lt_salesorderitem_key_data type standard table of gty_cds_views-salesorderitem,
          ls_todo_list               type /iwbep/if_v4_requ_basic_ref_l=>ty_s_todo_list.
    "generic data types
    data: ls_done_list         type /iwbep/if_v4_requ_basic_ref_l=>ty_s_todo_process_list,
          lv_nav_property_name type /iwbep/if_v4_med_element=>ty_e_med_internal_name.

    " Get the request options the application should/must handle
    io_request->get_todos( importing es_todo_list = ls_todo_list ).

    if ls_todo_list-process-source_key_data = abap_true.
      io_request->get_source_key_data( importing es_source_key_data =  ls_salesorder_key_data ).
      ls_done_list-source_key_data = abap_true.
    endif.

    io_request->get_navigation_prop( importing ev_navigation_prop_name = lv_nav_property_name ).

    case lv_nav_property_name.
      when gcs_nav_prop_names-internal-salesorder_to_items.

        select salesorder , salesorderitem from ze2e001_c_salesorderitem
        into corresponding fields of table @lt_salesorderitem_key_data
        where salesorder = @ls_salesorder_key_data-salesorder.

        io_response->set_target_key_data( lt_salesorderitem_key_data ).

      when others.

        raise exception type zcx_e2e001_odata_v4_so
          exporting
            http_status_code = zcx_e2e001_odata_v4_so=>gcs_http_status_codes-sv_not_implemented.

    endcase.

    " Report list of request options handled by application
    io_response->set_is_done( ls_done_list ).


  endmethod.

Service registration

What is left is to do the following.

  1. Register a service group ZE2E001 using transaction /iwbep/v4_admin
  2. Publish the service group using transaction /iwfnd/v4_admin
  3. Register the service in the service group ZE2E001 with the name ZE2E001_SALESORDER

For more details see the SAP Online Help.

Field Value
 Service ID  ZE2E001_SALESORDER
 Service Version  1
 Model Provider Class  ZCL_E2E001_ODATA_V4_SO_MODEL
 Data Provider Class  ZCL_E2E001_ODATA_V4_SO_DATA
Description OData V4 demo service
Package ZE2E001

 

Outlook

In the following blogs I will explain:

  • the basic interface methods for create, update and delete
  • the intermediate interface ($expand)
  • the advanced interface (navigation)

 

Assigned Tags

      10 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Syambabu Allu
      Syambabu Allu

      Hi Andre,

      Thanks a lot for sharing with all information.

      Most wanted blog on V4 development .

      Thanks,

      Syam

       

      Author's profile photo Ihar Partuhalau
      Ihar Partuhalau

       

      Hi Andre,

      Based on info you provided, you managed to connect to the Demo Gateway from Eclipse(ADT). What info you specified for that?

      Thank you,

      Ihar

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

      It is currently not possible to connect to ES5 via ADT. I am checking whether we can make this type of access possible

      Author's profile photo PRABHU DAS POLE
      PRABHU DAS POLE

      Hi Andree,

       

      Thanks alot for OData V4 implementation tutorials.

       

      Best Regards,

      Prabhu. Pole

      Author's profile photo Pavan Golesar
      Pavan Golesar

      Thanks for these wonderful list of odata v4  implementations

      -Pavan Golesar

      Author's profile photo Michael Walker
      Michael Walker

      Does the gateway support all the oData v4 functions such as "Groupby"?  If not, is there a list of what is supported in each netweaver version?

      Author's profile photo prem kumar
      prem kumar

      Hello,

      Very nice. How would we handle CDS with parameters in V4? Can you please explain ?

       

      Regards,

      Prem

      Author's profile photo Soham Kulkarni
      Soham Kulkarni

      Hello,

      Thank you for very informative blog and GitHub code samples. I have a quick question about $select, It is returning me key properties along with the property mentioned in $select.

      The method,

      io_request->get_selected_properties( IMPORTING et_selected_property = lt_selected_property ).

      Itself is retrieving all the properties marked as key (Here in sample, Seqnr and DateFile) along with the property mentioned in $select request (DataType).

      %24select%20with%20key%20property%20along%20with%20selected%20property

      $select with key property along with selected property

      Following is the sample data for all entity properties,

      "Seqnr" "1",
      "DateFile" "2019-10-31",
      "Datasourcedesc" "UKBABI",
      "DataType" "0",
      "MpCode" "0",
      "ProfitCentre" "50158-8883"

      Is there a way in OData v4 where I can get only the property which is requested in $select? I know in v2 only properties mentioned in $select are retrieved irrespective of key properties.

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

      This is by design since the key fields are usually always required.

      But you can easily remove the key fields from the internal table that is returned by the framework.

      Author's profile photo Soham Kulkarni
      Soham Kulkarni

      Thank you 🙂

      Removing the fields which are not required from internal table should work, but there could be an issue if $select has combination of key as well as well as non key properties.

      How can we determine the condition where the field is requested in $select and other key fields needs to be deleted?

      Thank You.

      <04/08/2021>
      Hi Andre Fischer,

      Can you please advise? your input on this could help me solve one of the blocker which I am currently facing.

      Also is there a way we can hide/disable a property from the entity and refrain it to be sent back in response? I ask this as I am looking for an alternative approach if $select behaves in the same way.

      Thank You.