In case a calculated field is required to deliver/filled by the CDS entity which is not persisted on the underlying database, you have several options:

  • CDS built-in functions and expressions like case etc.
  • HANA table functions
  • ABAP Classes

We are gonna examine the last option to see how ABAP can be involved. It only works via SADL, meaning in OData context forex and your class must implement the interface if_sadl_exit_calc_element_read.

The below example is based on a RAP entity. In non-RAP scenarios (like ABAP programming Model for Fiori) remove the virtual keyword and the ABAP: prefix from your Data Definition.
This is due the same class can be reused in different programming models and entities, if you go more dynamic in your class.

@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Messages'
@Metadata.ignorePropagatedAnnotations: true
define view entity ZC_Message
  as projection on ZI_Message
{
  key     Entitykey,
          RootKey,
          ParentKey,
          EntityAliasName,
          Id,
          Num,
          Severity,
          V1,
          V2,
          V3,
          V4,

          @ObjectModel.virtualElementCalculatedBy: 'ABAP:ZCL_MESSAGE_VIRTUAL'
  virtual messageText : zde_message_text,

          CreatedBy,
          CreatedAt,
          LastChangedBy,
          LastChangedAt,
          LocalLastChangedAt,
          MessageCriticallity
}

Our task was to fill in the message short text. That part is quite simple. More interesting are the hook methods which are invoked by SADL.

  • get_calculation_info
    you get the list which virtual properties are to be calculated. Simply when the user displays the field in Fiori, a request is sent to the backend to fetch the field which is to be calculated. If you find the virtual field name in it_requested_calc_elements then you have the possibility to receive the other field values in calculate method required for the calculation, because they might not be displayed by the user, therefore not part of the request.
  • calculate
    as the name suggest here you fill the virtual fields with values. You do this processing each record one by one

Few facts are here to be emphasized and which can cause runtime errors or incorrect calculation results ! These are the common pitfalls:

  • et_requested_orig_elements is sorted, do not append or insert by index, use INSERT INTO TABLE
  • ct_calculated_data contains only the virtual properties in the same order as in the CDS Data Definition. No need for structure with complete width / list of fields
  • SADL is matching the rows of ct_calculated_data to it_original_data by index => always append to ct_calculated_data in the same order as in the it_original_data.
  • Big surprises moving strings by dynamic programming
    When the CDS virtual property is not string, but of type char with limited length, then moving string values into it using a field-symbol ( made with assign component 'FIELDNAME' of structure statement forex) will cause SADL and the GateWay to fill the response structure incorrectly ! Instead of getting a technical error message, fields next in structure get overriden with the rest of the string if that is longer than your virtual property. You may get completely inaccurate business error messages or inconsistent data in the GateWay response. In that case ensure before moving, that you do the correct conversion (using CONV) to the target type defined in the CDS Entity.
"! <p class="shorttext synchronized" lang="en">Message - Virtual Fields</p>
CLASS zcl_message_virtual DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.

    INTERFACES if_sadl_exit .
    INTERFACES if_sadl_exit_calc_element_read .
  PROTECTED SECTION.
  PRIVATE SECTION.

    TYPES:
      BEGIN OF ty_message_virt_prop,
        messageText TYPE zde_message_text,
      END OF ty_message_virt_prop.

    CONSTANTS:
      BEGIN OF co_field_name,
        messageText TYPE string VALUE 'MESSAGETEXT' ##NO_TEXT,
        id          TYPE string VALUE 'ID' ##NO_TEXT,
        num         TYPE string VALUE 'NUM' ##NO_TEXT,
        severity    TYPE string VALUE 'SEVERITY' ##NO_TEXT,
        v1          TYPE string VALUE 'V1' ##NO_TEXT,
        v2          TYPE string VALUE 'V2' ##NO_TEXT,
        v3          TYPE string VALUE 'V3' ##NO_TEXT,
        v4          TYPE string VALUE 'V4' ##NO_TEXT,
      END OF co_field_name.
ENDCLASS.



CLASS zcl_message_virtual IMPLEMENTATION.


  METHOD if_sadl_exit_calc_element_read~calculate.
    DATA:
      records                 TYPE STANDARD TABLE OF ZC_Message WITH EMPTY KEY,
      virtual_properties      TYPE STANDARD TABLE OF ty_message_virt_prop,
      virtual_property_record TYPE ty_message_virt_prop.

    records = CORRESPONDING #( it_original_data ).

    LOOP AT records ASSIGNING FIELD-SYMBOL(<record>).
      CLEAR virtual_property_record.
      MESSAGE ID <record>-Id TYPE <record>-Severity NUMBER <record>-Num
          WITH <record>-v1
          <record>-v2
          <record>-v3
          <record>-v4 INTO virtual_property_record-messagetext.

      APPEND virtual_property_record TO virtual_properties.
    ENDLOOP.

    IF sy-subrc = 0.
      ct_calculated_data[] = virtual_properties[].
    ENDIF.

  ENDMETHOD.


  METHOD if_sadl_exit_calc_element_read~get_calculation_info.

    LOOP AT it_requested_calc_elements ASSIGNING FIELD-SYMBOL(<calc_element>).
      CASE <calc_element>.
        WHEN co_field_name-messagetext.
          INSERT:
            co_field_name-id INTO TABLE et_requested_orig_elements,
            co_field_name-num INTO TABLE et_requested_orig_elements,
            co_field_name-severity INTO TABLE et_requested_orig_elements,
            co_field_name-v1 INTO TABLE et_requested_orig_elements,
            co_field_name-v2 INTO TABLE et_requested_orig_elements,
            co_field_name-v3 INTO TABLE et_requested_orig_elements,
            co_field_name-v4 INTO TABLE et_requested_orig_elements.
      ENDCASE.
    ENDLOOP.

  ENDMETHOD.
ENDCLASS.

Share this content: