You may run into a scenario where requirements placed on your CDS data model are very difficult or impossible to meet with basic CDS functionality.
Examples of such requirements are:
The use of virtual elements or custom entities allows you to implement such requirements for your data model. This blog post will introduce you to these concepts. In addition, the use of CDS table functions based on ABAP-managed database procedures and SQLScript is possible, but we don’t use them here.
Virtual elements are used if you want to include elements in your CDS data model that aren’t available in the persistence data model of the database, but are calculated using (complex) ABAP program logic. The calculation is performed in ABAP classes that implement interfaces provided for this purpose.
Calculations Outside the Database: The virtual elements are calculated on the ABAP application server or the ABAP platform. Note that this means you don’t use the code pushdown capability for these virtual elements.
You can define a virtual element in the data model by specifying a designated annotation. To specify the implementing class you need to specify another annotation. To illustrate this, we’ll extend the CDS view ZI_Flight- Detail with another field that should contain the day of the week for a flight date. The day of the week is determined using the function block GET_WEEKDAY_NAME.
@ObjectModel.virtualElement: true
@ObjectModel.virtualElementCalculatedBy:
'ABAP:ZCL_FLIGHTDETAIL_CALC_EXIT'
cast ( '' as langt ) as FlightDateWeekday,
The first of the two annotations of the ObjectModel domain identifies the field as a virtual field; with the second annotation, you specify the ZCL_ FLIGHTDETAIL_CALC_EXIT class as the implementation class. In this class, you implement the interface IF_SADL_EXIT_CALC_ELEMENT_READ with the following methods:
Below shows a possible implementation of the calculation.
CLASS zcl_flightdetail_calc_exit DEFINITION
PUBLIC
FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_sadl_exit_calc_element_read.
* INTERFACES if_sadl_exit_filter_transform.
* INTERFACES if_sadl_exit_sort_transform.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_flightdetail_calc_exit IMPLEMENTATION.
METHOD if_sadl_exit_calc_element_read~get_calculation_info.
IF iv_entity <> 'ZI_FLIGHTDETAIL'.
RAISE EXCEPTION TYPE zcx_sadl_exit.
ENDIF.
LOOP AT it_requested_calc_elements
ASSIGNING FIELD-SYMBOL(<fs_calc_element>).
CASE <fs_calc_element>.
WHEN 'FLIGHTDATEWEEKDAY'.
APPEND 'FLIGHTDATE' TO et_requested_orig_elements.
WHEN OTHERS.
RAISE EXCEPTION TYPE zcx_sadl_exit.
ENDCASE.
ENDLOOP.
ENDMETHOD.
METHOD if_sadl_exit_calc_element_read~calculate.
DATA lt_original_data
TYPE STANDARD TABLE OF zi_flightdetail
WITH DEFAULT KEY.
lt _original_data = CORRESPONDING #( it_original_data ).
LOOP AT lt_original_data
ASSIGNING FIELD-SYMBOL(<fs_original_data>).
CALL FUNCTION 'GET_WEEKDAY_NAME'
EXPORTING
date = <fs_original_data>-FlightDate
language = sy-langu
IMPORTING
longtext = <fs_original_data>-FlightDateWeekday
EXCEPTIONS
calendar_id = 1
date_error = 2
not_found = 3
wrong_input = 4
OTHERS = 5.
IF sy-subrc <> 0.
* MESSAGE ID SY-MSGID TYPE SY-MSGTY NUMBER SY-MSGNO
* WITH SY-MSGV1 SY-MSGV2 SY-MSGV3 SY-MSGV4.
ENDIF.
ENDLOOP.
ct_calculated_data = CORRESPONDING #( lt_original_data ).
ENDMETHOD.
ENDCLASS.
Tip: Interfaces for Filter and Sort Functions: The interfaces IF_SADL_EXIT_FILTER_TRANSFORM and IF_SADL_EXIT_SORT_TRANSFORM are used to enable filter and sort functions for a virtual element. You can define the responsible implementation class via the annotations @ObjectModel.filter.transformedBy or @ObjectModel.sort.transformedBy.
Since the virtual element is calculated at the service level on the ABAP platform and not at the database level, a SELECT statement in ABAP SQL on the CDS view returns an initial value for the virtual element (the same applies to the data preview function in ADT). However, the annotations are evaluated by the SADL framework, so when queried via an OData entity based on the CDS data model, the computation is performed by the associated implementation class. Therefore, one way you can perform a test is to publish your CDS view as an OData service (with the annotation @OData.publish: true at the view level) and then test this OData service in the SAP Gateway client (Transaction /IWFND/MAINT_SERVICE). The figure below shows the result of this test in our example.
Typing a Virtual Element in a CDS Projection View: The labeling and typing of a virtual element in a CDS projection view in the ABAP RESTful application programming model differ slightly in their syntax from those described here. In the example, they would look as follows:
virtual FlightDateWeekday: langt
The annotation @ObjectModel.virtualElement: true is omitted.
For read access (i.e., a query) to CDS data models, the ABAP RESTful application programming model distinguishes between two approaches:
Below compares these two concepts.
CDS custom entities thus allow you to implement your own data selection in ABAP. At the CDS level, only the definition of the interface remains.
Typical use cases for a CDS custom entity are:
CDS custom entities are used in the RAP framework as the basis for data selection of unmanaged queries. However, they can also be used in transactional RAP applications within business object modeling. In ABAP CDS, CDS custom entities can be specified as the target of associations. However, they can’t be used as a data source for SELECT statements in other CDS entities.
In ABAP programs, a CDS custom entity can also not be used as a data source of SELECT statements. However, the structured type of the CDS custom entity is known in ABAP programs and can be used, for example, after the TYPE addition.
A CDS custom entity can be defined via the define custom entity statement in the DDL source code. The easiest way to do this is to use the Define Custom Entity with Parameters template in ADT. Below shows the syntax for defining a CDS custom entity using a simple example.
@EndUserText.label: 'CDS Custom Entity Demo'
@ObjectModel.query.implementedBy: 'ABAP:ZCL_CUST_ENTITY_DEMO'
define custom entity zcust_entity_demo
// with parameters parameter_name : parameter_type
{
// Element list, Element annotations
@UI.selectionField: [{position: 10}]
@UI.lineItem: [{position: 10}]
key UserName : xubname;
@UI.lineItem: [{position: 20}]
FirstName : ad_namefir;
@UI.lineItem: [{position: 30}]
LastName : ad_namelas;
FullName : ad_namtext;
}
The annotation @ObjectModel.query.ImplementedBy at the view level references the ABAP class in which the query is implemented. Since the data types can’t be derived from an underlying database table or CDS entity, the element list of a CDS custom entity must be typed. Optionally, you can declare view-level and element-level metadata, a parameter list, or associations in the element list.
The query implementation class implements the interface IF_RAP_QUERY_PROVIDER and its Select method. Note that all functions (e.g., sorting, filtering, paging) that are provided by default in a managed query must be explicitly programmed here. In the following example, SAP user data is supposed to be queried via the Business Application Programming Interface (BAPI) BAPI_USER_GETLIST. The listing below shows an excerpt of the implementation. For the complete implementation of the example, you should refer to the SAP documentation on the ABAP RESTful application programming model at http://s-prs.de/v868503.
CLASS zcl_cust_entity_demo DEFINITION
...
PUBLIC SECTION.
INTERFACES if_rap_query_provider.
...
ENDCLASS.
CLASS zcl_cust_entity_demo IMPLEMENTATION.
METHOD if_rap_query_provider~select.
DATA(lv_top) = io_request->get_paging( )->get_page_size( ).
DATA(lv_skip) = io_request->get_paging( )->get_offset( ).
DATA(lt_clause) = io_request->get_filter( )->get_as_sql_
string( ).
...
DATA lt_userlist TYPE STANDARD TABLE OF bapiusname.
DATA lt_result TYPE STANDARD TABLE OF zcust_entity_demo.
...
TRY.
DATA(lt_filter_cond) = io_request->get_filter( )->
get_as_ranges( ).
CATCH cx_rap_query_filter_no_range INTO DATA(lx_no_sel_option).
ENDTRY.
TRY.
IF io_request->is_data_requested( ).
CALL FUNCTION 'BAPI_USER_GETLIST'
EXPORTING
with_username = abap_true
TABLES
userlist = lt_userlist
return = lt_bapiret.
* Filter
LOOP AT lt_userlist INTO DATA(ls_userlist).
DATA(lv_tabix) = sy-tabix.
LOOP AT lt_filter_cond INTO DATA(ls_filter_cond).
CASE ls_filter_cond-name.
...
* Sorting
IF lt_sort IS NOT INITIAL.
LOOP AT lt_sort INTO DATA(ls_sort).
...
* Paging
IF lv_top > 0.
LOOP AT lt_userlist INTO ls_userlist FROM lv_skip
+ 1 TO ( lv_skip + lv_top ).
MOVE-CORRESPONDING ls_userlist TO ls_result.
APPEND ls_result TO lt_result.
...
* Count
IF io_request->is_total_numb_of_rec_requested( ).
io_response->set_total_number_of_records( lines(
lt_result ) ).
ENDIF.
io_response->set_data( lt_result ).
ELSE.
* no data is requested
ENDIF.
CATCH cx_rap_query_provider INTO DATA(lx_exc). "error handling
ENDTRY.
ENDMETHOD.
ENDCLASS.
After creating a service definition and a service binding, you can test the query directly in ADT. The final figure shows the result of the query in the data preview.
Editor’s note: This post has been adapted from a section of the book ABAP RESTful Application Programming Model: The Comprehensive Guide by Lutz Baumbusch, Matthias Jäger, and Michael Lensch.