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

RAP - Custom Entity

253

In diesem Artikel schauen wir uns eine spezielle Form von Entitäten im RAP Modell an und wie wir sie nutzen können.

Werbung


In diesem Artikel werden wir uns die Custom Entity etwas näher anschauen, wie die Datenbeschaffung läuft und wofür wir diese Entitäten verwenden können. Dabei werden wir auch auf die Unterschiede zur klassischen Entität eingehen und diese näher beleuchten.

 

Einleitung

Die Custom Entity unterscheidet sich von einer CDS Entität durch die Datenbeschaffung. Hier hast du die Möglichkeit durch eine Query Klasse ABAP Code zu implementieren und dort die Daten zu ermitteln und zu übergeben. In der nachfolgenden Abbildung siehst du auf der linken Seite das klassische Modell wie die Daten aus einer Tabelle kommen. Auf der rechten Seite die Custom Entity mit einer Query-Klasse und der Datenbeschaffung über ein Consumption Modell:

 

Die Daten können dabei lokal abgeleitet werden oder über eine On-Premise Schnittstelle geladen werden. Da du hier in ABAP unterwegs bist, hast du freie Wahl der Methodik.

 

Custom Entity

Legen wir im ersten Schritt nun einmal eine neue Entität an. Ziel ist es, die Daten aus On-Premise zur Verfügung zu stellen, die wir in diesem Artikel angebunden haben. Dazu definieren wir eine neue CDS Entität in unserem Demo-Paket ohne ein entsprechendes Referenzobjekt.

 

Nach der Bestätigung des Transportes können wir noch ein entsprechendes Template auswählen, hier gibt es aber nur "Extend custom entity" und "Custom Entity with Parameters", deshalb wählen wir die zweite Art:

 

Im nächsten Schritt können wir dann den CDS View spezifizieren und die Elemente aus der abstrakten Entität "ZBS_RAP_COMPANYNAMES" übernehmen. Die Entität wurde automatisch mit dem Service Consumption Model erzeugt. Die "_VC" Elemente (Value Control) benötigen wir dabei nicht. Dann müssen wir noch eine Annotation für die Query Klasse ergänzen, der View sieht nun wie folgt aus:

@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 );
}

 

Bevor du allerdings den CDS View aktivieren kannst, musst du die Query-Klasse erzeugen, da sonst der Compiler einen Fehler wirft. Die leere Klasse sieht wie folgt aus und implementiert das Interface "IF_RAP_QUERY_PROVIDER":

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.

 

Datenbeschaffung

Für die Datenbeschaffung müssen wir nun die SELECT Methode implementieren, dazu schauen wir uns die Schnittstelle an:

 

Aufbau

Es gibt ein REQUEST Objekt, welches den Anfragekontext an die Klasse übergibt. Hier werden Informationen wie Paging, angefragte Felder und Einschränkungen mitgeliefert. Das RESPONSE Objekt erwartet dann das Ergebnis dieser Anfrage. Dazwischen können wir nun unsere Logik implementieren.

 

Proxy Objekt

Wie bereits in den vorhergehenden Beispielen benötigen wir nun ein Zugriff auf die Schnittstelle, dazu müssen wir im ersten Schritt einen Proxy erzeugen, was wir wieder über eine Destination tun, und im nächsten Schritt dann eine Leseanfrage öffnen:

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

 

Request zerlegen

Im nächsten Schritt bauen wir die Anfrage Richtung On-Premise auf, indem wir alle angefragten Elemente und Eigenschaften aus IO_REQUEST extrahieren. Dazu das folgende Coding:

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

 

Die Elemente übernehmen wir nun Schritt für Schritt und übergeben sie an die Anfrage. Im ersten Schritt übernehmen wir alle Filter und bauen über die Filter Factory den Zugriff auf, diesen übergeben wir zum Abschluss an die Anfrage:

" 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 der Abfrage können auch nur bestimmte Felder abgefragt werden, die die Tabelle befüllt, dann übergeben wir sie an die entsprechende Methode. Wenn ein OData Service viele Felder in der Entität enthält, empfiehlt es sich immer die Anzahl der Felder einzuschränken:

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

 

Ebenfalls können wir eine Sortierung mitgeben, wenn diese angefragt wurde. Ist vor allem wichtig, wenn wir mit einem Paging auf den Daten arbeiten, ansonsten können falsche Ergebnisse zurückgegeben werden.

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

 

Werden überhaupt Daten angefragt oder wird nur die Anzahl der Datensätze im Backend benötigt? Dazu werden die beiden Flags abgefragt und die Methoden in der Abfrage versorgt. Werden Daten abgerufen, übergeben wir auch die entsprechenden TOP/SKIP Werte aus der originalen Abfrage:

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

 

Zum Abschluss führen wir die Anfrage Richtung On-Premise aus und holen uns die Daten ins Programm:

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

 

Response versorgen

Als letzten Schritt müssen wir noch IO_RESPONSE der SELECT Methode befüllen. Wir prüfen dazu, ob Daten und Count angefragt wurden und übergeben die Elemente.

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

 

Hinweis: Der Aufbau der Anfrage Richtung On-Premise ist recht komplex, kann aber mit Leichtigkeit in eine wiederverwendbare Klasse ausgelagert werden, was wir auch empfehlen, da sich damit viel Coding sparen lässt, welches sonst redundant angelegt wird (DRY Prinzip).

 

Werthilfe

Eine Möglichkeit zur Nutzung und zum Test einer Custom Entity ist die Verwendung als Werthilfe auf einem Feld. Dazu erweitern wir den Core Data Service "ZBS_C_RAPPartner" aus unserer RAP Serie. Das Feld PartnerName erweitern wir um eine Suchhilfe mit unserer Custom Entity als Suchelement, dazu dieser Auszug aus dem 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 unserer App gibt es nun auf dem Feld "Name" eine neue Suchhilfe, die wir über das Icon im hinteren Bereich öffnen können:

 

Da wir in der Entität keine weiteren Einschränkungen vorgenommen haben oder UI Annotationen gepflegt haben, werden die Feldnamen als Überschriften verwendet. Beim Öffnen der Suchhilfe werden die Daten aus On-Premise geladen und in der Liste angezeigt. Über die Felder oben, können weitere Einschränkungen durchgeführt werden.

 

Debugging

Wie können wir nun die Entität debuggen? Dazu wollen wir uns einmal anschauen, was über den Request an die Klasse übergeben wird. Dazu setzen wir einen Breakpoint auf die Filtererstellung, direkt nach dem Lesen des IO_REQUEST Objekts. In der Suchhilfe filtern wir die Branche auf "*soft* um alle Firmen zu ermitteln, die mit Software zu tun haben:

 

Nachdem wir die Suche nun über "Go" gestartet haben, laden wir im Debugger in Eclipse und können uns die Werte etwas genauer anschauen.

 

Hinweis: Die Ermittlung der Daten funktioniert nur über den OData Service, bei der Anzeige der Daten im Data Preview wird eine leere Tabelle angezeigt und die Ermittlungslogik nicht durchlaufen.

 

Vollständiges Beispiel

Die vollständige Klasse für die Query Implementierung sieht nun wie folgt aus, dabei musst du beachten, eine existierende Destination zu verwenden.

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.

 

Fazit

Die Custom Entity erweitert RAP, um eine Möglichkeit Daten zur Verfügung zu stellen, dabei ist die Implementierung durch ABAP Code einfach und flexibel. Damit kannst du Core Data Services als Datenquellen für Wertehilfen oder in Services zur Verfügung stellen.


Enthaltene Themen:
RAPBTPCustom Entity
Kommentare (0)



Und weiter ...

Bist du zufrieden mit dem Inhalt des Artikels? Wir posten jeden Freitag neuen Content im Bereich ABAP und unregelmäßig in allen anderen Bereichen. Schaue bei unseren Tools und Apps vorbei, diese stellen wir kostenlos zur Verfügung.


RAP - Custom Entity Wertehilfe (Deep Dive)

Kategorie - ABAP

Mit der Custom Entity hast du in RAP die meisten Freiheiten in der Entwicklung von ABAP Cloud Anwendungen, doch wie sieht es mit potentiellen Fehlern aus?

12.07.2024

RAP - Deep Action in OData v4

Kategorie - ABAP

In diesem Artikel schauen wir uns einmal Aktionen mit tiefen Strukturen an, wie wir sie erzeugen und Daten an einen API Endpunkt übergeben können.

24.05.2024

BTP - Anbindung On-Premise (Consumption Model v2)

Kategorie - ABAP

In diesem Artikel wollen wir noch einmal einen Update zur Anbindung von On-Premise Systemen geben und wie dies mit einem Communication Arrangement durchgeführt wird.

15.12.2023

RAP - App Count anzeigen (Kachel)

Kategorie - ABAP

In diesem Beispiel geht es um die Anzeige eines Zählers auf der Kachel einer Fiori Elements Anwendung und wie sich so etwas umsetzen lässt.

06.10.2023

RAP - Generator (Fiori)

Kategorie - ABAP

In diesem Artikel schauen wir uns den komplexeren RAP Generator als Fiori Elements Anwendung an und welche Vorteile du damit hast.

15.09.2023