This is a test message to test the length of the message box.
Login
ABAP RAP Custom Entity
Created by Software-Heroes

RAP - Custom Entity

7540

In this article we look at a special form of entities in the RAP model and how we can use them.



In this article we will take a closer look at the custom entity, how the data is retrieved and what we can use these entities for. We will also go into the differences to the classic entity and examine them in more detail.

 

Introduction

The custom entity differs from a CDS entity in terms of data acquisition. Here you have the option of implementing ABAP code using a query class and determining and transferring the data there. In the figure below you can see the classic model of how the data comes from a table on the left-hand side. On the right side the custom entity with a query class and data acquisition via a consumption model:

 

The data can be derived locally or loaded via an on-premise interface. Since you are working in ABAP here, you have a free choice of methodology.

 

Custom Entity

In the first step, let's create a new entity. The goal is to provide the data from on-premises that we have linked in this article. To do this, we define a new CDS entity in our demo package without a corresponding reference object.

 

After confirming the transport, we can still select the appropriate template, but here there are only "Extend custom entity" and "Custom entity with parameters", so we choose the second type:

 

In the next step, we can then specify the CDS view and adopt the elements from the abstract entity "ZBS_RAP_COMPANYNAMES". The entity was automatically generated using the Service Consumption Model. We do not need the "_VC" elements (Value Control). Then we have to add an annotation for the query class, the view now looks like this:

@EndUserText.label: 'Custom entity for company names'
@ObjectModel.query.implementedBy: 'ABAP:ZCL_BS_DEMO_CUST_CNAME_QUERY'
define custom entity ZBS_I_RAPCustomEntityCNames
{
  key CompanyName        : abap.char( 60 );
      Branch             : abap.char( 50 );
      CompanyDescription : abap.char( 255 );
}

 

However, before you can activate the CDS view, you must create the query class, otherwise the compiler will throw an error. The empty class looks like this and implements the "IF_RAP_QUERY_PROVIDER" interface:

CLASS zcl_bs_demo_cust_cname_query DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    INTERFACES:
      if_rap_query_provider.

  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.


CLASS zcl_bs_demo_cust_cname_query IMPLEMENTATION.
  METHOD if_rap_query_provider~select.
  ENDMETHOD.
ENDCLASS.

 

Data acquisition

For data retrieval we now have to implement the SELECT method, so let's look at the interface:

 

Structure

There is a REQUEST object that passes the request context to the class. Information such as paging, requested fields and restrictions are provided here. The RESPONSE object then awaits the result of this query. In between we can now implement our logic.

 

Proxy Object

As in the previous examples, we now need access to the interface. In the first step, we have to create a proxy, which we do again via a destination, and then open a read request in the next step:

DATA(lo_request) = get_proxy( )->create_resource_for_entity_set( c_entity )->create_request_for_read( ).

 

Disassemble request

In the next step, we build the request towards on-premises by extracting all requested elements and properties from IO_REQUEST. The following code for this:

DATA(lt_filter_condition) = io_request->get_filter( )->get_as_ranges( ).
DATA(lt_requested_elements) = io_request->get_requested_elements( ).
DATA(lt_sort_elements) = io_request->get_sort_elements( ).
DATA(ld_skip) = io_request->get_paging( )->get_offset( ).
DATA(ld_top) = io_request->get_paging( )->get_page_size( ).
DATA(ld_is_data_requested)  = io_request->is_data_requested( ).
DATA(ld_is_count_requested) = io_request->is_total_numb_of_rec_requested( ).

 

We now accept the elements step by step and transfer them to the request. In the first step, we take over all filters and set up access via the filter factory, which we then transfer to the request:

" Build filter condition
DATA(lo_filter_factory) = lo_request->create_filter_factory( ).
LOOP AT lt_filter_condition INTO DATA(ls_filter_condition).
  DATA(lo_filter_node) = lo_filter_factory->create_by_range(
    iv_property_path = ls_filter_condition-name
    it_range         = ls_filter_condition-range
  ).

  IF lo_root_filter_node IS INITIAL.
    lo_root_filter_node = lo_filter_node.
  ELSE.
    lo_root_filter_node = lo_root_filter_node->and( lo_filter_node ).
  ENDIF.
ENDLOOP.

" Set filter
IF lo_root_filter_node IS NOT INITIAL.
  lo_request->set_filter( lo_root_filter_node ).
ENDIF.

 

In the query, only certain fields that the table fills can be queried, then we pass them to the appropriate method. If an OData Service contains many fields in the entity, it is always a good idea to limit the number of fields:

" Set requested fields
IF lt_requested_elements IS NOT INITIAL.
  lo_request->set_select_properties( CORRESPONDING #( lt_requested_elements ) ).
ENDIF.

 

We can also provide a sorting if this has been requested. Is especially important when we work with a paging on the data, otherwise wrong results can be returned.

" Set Sorting
IF lt_sort_elements IS NOT INITIAL.
  lo_request->set_orderby( CORRESPONDING #( lt_sort_elements MAPPING property_path = element_name ) ).
ENDIF.

 

Is data requested at all or is only the number of data records required in the backend? To do this, the two flags are queried and the methods in the query are supplied. When data is retrieved, we also pass the corresponding TOP/SKIP values from the original query:

" Data requested -> Set top/skip values
IF ld_is_data_requested = abap_true.
  lo_request->set_skip( CONV #( ld_skip ) ).

  IF ld_top > 0.
    lo_request->set_top( CONV #( ld_top ) ).
  ENDIF.
ELSE.
  lo_request->request_no_business_data(  ).
ENDIF.

" Count is requested
IF ld_is_count_requested = abap_true.
  lo_request->request_count(  ).
ENDIF.

 

Finally, we execute the request towards on-premises and get the data into the program:

" Execute and return data
DATA(lo_response) = lo_request->execute( ).
lo_response->get_business_data( IMPORTING et_business_data = et_result ).
ed_count = lo_response->get_count( ).

 

Fill Response

As a last step we have to fill IO_RESPONSE of the SELECT method. We check whether data and count have been requested and transfer the elements.

" Handle data over to respone
IF io_request->is_total_numb_of_rec_requested(  ).
  io_response->set_total_number_of_records( ld_count ).
ENDIF.

IF io_request->is_data_requested(  ).
  io_response->set_data( lt_result ).
ENDIF.

 

Hint: The structure of the request in the direction of on-premises is quite complex, but can easily be outsourced to a reusable class, which we also recommend, as it saves a lot of coding that would otherwise be created redundantly (DRY principle).

 

Value help

One way to use and test a custom entity is to use it as an input help in a field. To do this, we are expanding the core data service "ZBS_C_RAPPartner" from our RAP series. We add a search help to the PartnerName field with our custom entity as a search element, as well as this excerpt from the CDS view:

define root view entity ZBS_C_RAPPartner
  provider contract transactional_query
  as projection on ZBS_I_RAPPartner
{
  key PartnerNumber,
      @Consumption.valueHelpDefinition: [{ entity: { name: 'ZBS_I_RAPCustomEntityCNames', element: 'CompanyName' } }]
      PartnerName,

 

In our app there is now a new search help in the "Name" field, which we can open using the icon in the back area:

 

Since we have not made any further restrictions in the entity or maintained UI annotations, the field names are used as headings. When opening the search help, the data is loaded from on-premises and displayed in the list. Further restrictions can be carried out using the fields above.

 

Debugging

Now how can we debug the entity? To do this, we want to take a look at what is passed to the class via the request. To do this, we set a breakpoint on the filter creation, right after reading the IO_REQUEST object. In the search help, we filter the industry to "*soft*" to find all companies related to software:

 

Now that we have started the search with "Go", we load the debugger in Eclipse and can take a closer look at the values.

 

Hint: The determination of the data only works via the OData Service, when the data is displayed in the data preview, an empty table is displayed and the determination logic is not run through.

 

Complete example

The complete class for the query implementation now looks like this, but you have to take care to use an existing destination.

CLASS zcl_bs_demo_cust_cname_query DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    INTERFACES:
      if_rap_query_provider.

  PROTECTED SECTION.
  PRIVATE SECTION.
    TYPES:
      tt_result TYPE STANDARD TABLE OF ZBS_I_RAPCustomEntityCNames WITH EMPTY KEY.

    CONSTANTS:
      c_destination TYPE string VALUE `<destination-service-id>`,
      c_entity      TYPE /iwbep/if_cp_runtime_types=>ty_entity_set_name VALUE 'COMPANYNAMES'.

    METHODS:
      get_proxy
        RETURNING VALUE(ro_result) TYPE REF TO /iwbep/if_cp_client_proxy,

      read_data_by_request
        IMPORTING
          io_request TYPE REF TO if_rap_query_request
        EXPORTING
          et_result  TYPE tt_result
          ed_count   TYPE int8.
ENDCLASS.


CLASS zcl_bs_demo_cust_cname_query IMPLEMENTATION.
  METHOD if_rap_query_provider~select.
    " Read data from OData on-premise
    read_data_by_request(
      EXPORTING
        io_request = io_request
      IMPORTING
        et_result  = DATA(lt_result)
        ed_count   = DATA(ld_count)
    ).

    " Handle data over to respone
    IF io_request->is_total_numb_of_rec_requested(  ).
      io_response->set_total_number_of_records( ld_count ).
    ENDIF.

    IF io_request->is_data_requested(  ).
      io_response->set_data( lt_result ).
    ENDIF.
  ENDMETHOD.


  METHOD get_proxy.
    TRY.
        DATA(lo_destination) = cl_http_destination_provider=>create_by_cloud_destination(
          i_name       = c_destination
          i_authn_mode = if_a4c_cp_service=>service_specific
        ).

        DATA(lo_client) = cl_web_http_client_manager=>create_by_http_destination( lo_destination ).

        ro_result = cl_web_odata_client_factory=>create_v2_remote_proxy(
          EXPORTING
            iv_service_definition_name = 'ZBS_DEMO_RAP_ONPREM_ODATA'
            io_http_client             = lo_client
            iv_relative_service_root   = '/sap/opu/odata/sap/ZBS_API_COMPANY_NAMES_O2' ).

      CATCH cx_root.
    ENDTRY.
  ENDMETHOD.


  METHOD read_data_by_request.
    DATA:
      lo_root_filter_node TYPE REF TO /iwbep/if_cp_filter_node.

    TRY.
        DATA(lo_request) = get_proxy( )->create_resource_for_entity_set( c_entity )->create_request_for_read( ).

        " Get informations from request
        DATA(lt_filter_condition) = io_request->get_filter( )->get_as_ranges( ).
        DATA(lt_requested_elements) = io_request->get_requested_elements( ).
        DATA(lt_sort_elements) = io_request->get_sort_elements( ).
        DATA(ld_skip) = io_request->get_paging( )->get_offset( ).
        DATA(ld_top) = io_request->get_paging( )->get_page_size( ).
        DATA(ld_is_data_requested)  = io_request->is_data_requested( ).
        DATA(ld_is_count_requested) = io_request->is_total_numb_of_rec_requested( ).

        " Build filter condition
        DATA(lo_filter_factory) = lo_request->create_filter_factory( ).
        LOOP AT lt_filter_condition INTO DATA(ls_filter_condition).
          DATA(lo_filter_node) = lo_filter_factory->create_by_range(
            iv_property_path = ls_filter_condition-name
            it_range         = ls_filter_condition-range
          ).

          IF lo_root_filter_node IS INITIAL.
            lo_root_filter_node = lo_filter_node.
          ELSE.
            lo_root_filter_node = lo_root_filter_node->and( lo_filter_node ).
          ENDIF.
        ENDLOOP.

        " Set filter
        IF lo_root_filter_node IS NOT INITIAL.
          lo_request->set_filter( lo_root_filter_node ).
        ENDIF.

        " Set requested fields
        IF lt_requested_elements IS NOT INITIAL.
          lo_request->set_select_properties( CORRESPONDING #( lt_requested_elements ) ).
        ENDIF.

        " Set Sorting
        IF lt_sort_elements IS NOT INITIAL.
          lo_request->set_orderby( CORRESPONDING #( lt_sort_elements MAPPING property_path = element_name ) ).
        ENDIF.

        " Data requested -> Set top/skip values
        IF ld_is_data_requested = abap_true.
          lo_request->set_skip( CONV #( ld_skip ) ).

          IF ld_top > 0.
            lo_request->set_top( CONV #( ld_top ) ).
          ENDIF.
        ELSE.
          lo_request->request_no_business_data(  ).
        ENDIF.

        " Count is requested
        IF ld_is_count_requested = abap_true.
          lo_request->request_count(  ).
        ENDIF.

        " Execute and return data
        DATA(lo_response) = lo_request->execute( ).
        lo_response->get_business_data( IMPORTING et_business_data = et_result ).
        ed_count = lo_response->get_count( ).

      CATCH cx_root.
    ENDTRY.
  ENDMETHOD.
ENDCLASS.

 

Conclusion

The Custom Entity extends RAP to provide a way to provide data while being easy and flexible to implement through ABAP code. This allows you to make Core Data Services available as data sources for input help or in services.


Included topics:
RAPBTPCustom Entity
Comments (0)



And further ...

Are you satisfied with the content of the article? We post new content in the ABAP area every Friday and irregularly in all other areas. Take a look at our tools and apps, we provide them free of charge.


RAP - Translation app (example)

Category - ABAP

Let's take a look at a practical example of developing a RAP application in the ABAP environment and how you can create an app with little effort.

08/02/2024

RAP - Custom Entity Value Help (Deep Dive)

Category - ABAP

With the Custom Entity you have the most freedom in RAP when developing ABAP Cloud applications, but what about potential errors?

07/12/2024

RAP - Deep Action in OData v4

Category - ABAP

In this article we will look at actions with deep structures, how we can create them and pass data to an API endpoint.

05/24/2024

BTP - Connect On-Premise (Consumption Model v2)

Category - ABAP

In this article we want to provide another update on the connection of on-premise systems and how this is done with a communication arrangement.

12/15/2023

RAP - Show app count (Tile)

Category - ABAP

This example is about displaying a counter on the tile of a Fiori Elements application and how such a thing can be implemented.

10/06/2023