Implementing the Behavior of Composite Business Objects

Objective

After completing this lesson, you will be able to access composite business objects using EML.

Read-by-Association Operations

Read by Association

When accessing a RAP business object via EML, you can use the associations defined in its behavior definition to read data from associated child entities that are part of the composition tree.

Note

It is also possible to read parent entities via the child entities. However, it is only supported from within the implementation class.

The read-by-association operation is implemented in READ ENTITIES or READ ENTITY by adding BY \<association_name> between the entity name and the field specification. In the example, the association name is _line.

The derived types for the read-by-association operation are defined by using <entity_name>\<association_name> instead of just the entity name.

Example of reading parent entity data and item data

It is possible to read parent entity instances and related child entity instances in the same EML statement. In the example, gt_keys is filled with one or several keys for instances of root entity Z##_R_Travel. The first part of the READ ENTITIES statement retrieves the data for the travel instances.

The second part reads data for all travel item instances that are related to the root entity instances via association _Item.

The addition LINK is only available in read-by-association operations and returns an internal table with keys of source entity instances and target entity instance. It can be used later to map the travels to the related items if more than one travel has been read.

Hint

If all you need are the keys of the travel items, you can omit the RESULT addition and only specify the LINK addition. It can help to improve performance.

Note

When combing several read and read-by-association operations in one statement, the response parameters, like, for example the FAILED parameter, only exist once.

Create-by-Association Operations

Create by Association

If an association is create-enabled in the behavior definition, you can use this association to create instances of the associated entity.

Note

Up to now, only compositions can be create-enabled. This means that the target of the association is always a composition child of the entity for which you execute the operation.

The create-by-association operation is implemented by adding BY \<association_name> after the keyword CREATE in MODIFY ENTITIES or MODIFY ENTITY. In the example, the association name is _Line.

The derived type for the create-by-association operation is defined by using <entity_name>\<association_name> instead of the entity name alone.

Derived type for create-by-association

The line type of the internal table that serves as import for EML operation create-by-association consists of two parts:

  • Elementary components to identify the instance of the parent entity. These fields are summarized in component group %tky.
  • Structure component %target, which is a structured component to specify the data for the new child entity instance, including a %control structure to specify which components are supplied.

The %cid_ref component is used to identify the parent entity instance in situations where the actual key is not yet available. It can be the case if the parent entity uses late numbering, or if the parent entity is created in the same EML statement as the child entity (deep create). In that case, the %cid component has to be filled with non initial values when creating the parent entity instances.

Similarly, the %cid component in the %target sub structure is used to set a temporary key for the new child entity instance, by which it can be identified until internal numbering provides the actual key.

Implement the Behavior of a Composite Business Object

In this exercise, you implement the behavior for the composite business object. You define and implement a validation for the child entity and a determination.

Note

In this exercise, replace ## with your group number.

Solution

Repository Object TypeRepository Object ID
CDS Behavior Definition (Model)/LRN/437E_R_TRAVEL
ABAP Class/LRN/BP_437E_R_TRAVEL

Task 1: Validate the Flight Date

Extend the behavior definition of your business object. For the child entity Z##_R_TravelItem, define a validation for the flight date (suggested name: validateFlightDate) that is always performed during the create operation, but during the update operation only if the user changed the value of the FlightDate field. Add the validation to the draft determine action Prepare and generate the implementation method. If the flight date is initial or if it lies in the past, add the child entity instance to the failed parameter and the reported parameter. Use suitable texts from the /LRN/CM_S4D437 class as message texts.

Hint

If you implemented the validateBeginDate validation, you can copy that implementation as a starting point. Just make sure you read the data of the child entity and not from the root entity and that you fill the item component of the response parameters and not the travel component.

Steps

  1. Edit the behavior definition on data model level Z##_R_TRAVEL. In the behavior definition for the child entity Z##_R_TravelItem, add a validation (suggested name: validateFlightDate) that is triggered by the create operation or changes to the FlightDate field.

    1. Adjust the code as follows:

      Code Snippet
      12345678
      field ( readonly ) AgencyId, TravelId; validation validateFlightDate on save { create; field FlightDate; }
  2. Add the new validation to the list of validations that are to be performed during the draft determine action Prepare of the root entity.

    Hint

    Address the validation in the following form: <child entity alias>~<validation name>.
    1. Adjust the code as follows:

      Code Snippet
      123456789
      draft determine action Prepare { validation validateDescription; validation validateCustomer; validation validateBeginDate; validation validateEndDate; validation validateDateSequence; validation Item~validateFlightDate; }

      Note

      If you did not implement the validations for the date fields, the code looks like this:
      Code Snippet
      123456
      draft determine action Prepare { validation validateDescription; validation validateCustomer; validation Item~validateFlightDate; }
  3. Activate the behavior definition and use a quick fix to generate the implementation method for the validation in the local handler class for flight travel items.

    1. Press Ctrl + F3 to activate the development object.

    2. In the validation definition statement, place the cursor on the name of the validation and press Ctrl + 1.

    3. From the list of available quick fixes, choose Add method for validation validateflightdate of entity z##_r_travelitem in new local class.

  4. Edit the validation implementation method. Read the flight date of all affected flight travel items. Store the result in an inline declared internal table (suggested name: items). Then loop over the flight travel items and assign an inline declared field symbol (suggested name: <item>).

    1. Between METHOD validateFlightDate. and ENDMETHOD., add the following code:

      Code Snippet
      123456789
      READ ENTITIES OF Z##_R_Travel IN LOCAL MODE ENTITY Item FIELDS ( FlightDate ) WITH CORRESPONDING #( keys ) RESULT DATA(items). LOOP AT items ASSIGNING FIELD-SYMBOL(<item>). ENDLOOP.
  5. At the beginning of the loop, before the actual check, add a new row to the item component of the reported parameter. In this row, fill %tky with the key of the affected instance and %state_area with a non-initial value (suggested value: FLIGHTDATE) .

    Hint

    As before, we recommend that you define a constant for the %state_area value (suggested name: c_area).
    1. At the beginning of the method, add the following code:

      Code Snippet
      1
      CONSTANTS c_area TYPE string VALUE `FLIGHTDATE`.
    2. Inside the loop, add the following code:

      Code Snippet
      123
      APPEND VALUE #( %tky = <item>-%tky %state_area = c_area ) TO reported-item.
  6. After the above implemented APPEND statement, add an IF block to the body of the loop: If the FlightDate field contains the initial value, add the key of the current travel item instance to the item component of parameter failed. In the same IF block, add a new row to the item component of the reported parameter. In this row, fill %tky with the key of the affected instance, %msg with an instance of the /LRN/CM_S4D437 class, using a suitable constant for the textid parameter, %state_area with the value of the c_area constant and %element with a flag to link the error message to the FLightDate field.

    1. Inside the loop, add the following code:

      Code Snippet
      12345678910111213
      IF <item>-FlightDate IS INITIAL. APPEND VALUE #( %tky = <item>-%tky ) TO failed-item. APPEND VALUE #( %tky = <item>-%tky %msg = NEW /lrn/cm_s4d437( /lrn/cm_s4d437=>field_empty ) %element-FlightDate = if_abap_behv=>mk-on %state_area = c_area ) TO reported-item. ENDIF.
  7. Add an ELSEIF branch that is executed, if the flight date lies before the system date. Copy the code from the IF branch to the ELSEIF branch and adjust the error message text.

    1. Before ENDIF., add the following code:

      Code Snippet
      1234567891011
      ELSEIF <item>-FlightDate < cl_abap_context_info=>get_system_date( ). APPEND VALUE #( %tky = <item>-%tky ) TO failed-item. APPEND VALUE #( %tky = <item>-%tky %msg = NEW /lrn/cm_s4d437( /lrn/cm_s4d437=>flight_date_past ) %element-FlightDate = if_abap_behv=>mk-on %state_area = c_area ) TO reported-item.
  8. Activate the ABAP class and retest the preview for the OData UI service.

    1. Press Ctrl + F3 to activate the development object.

    2. Open the service binding, place the cursor on the root entity and choose Preview....

    3. In the preview, display the details for one of the travels, change to edit mode, and choose Create to create a new flight travel item.

    4. Leave all input fields empty, especially the Flight Date field, and choose Apply.

    5. Back on the object page of the travel, choose Save to trigger the execution of the validations.

      Result

      You see the (transition) message with the generic error text.
  9. Extend the READ ENTITIES statement, so that it also reads the AgencyId and TravelId fields.

    1. Adjust the code as follows:

      Code Snippet
      12345
      READ ENTITIES OF Z##_R_Travel IN LOCAL MODE ENTITY Item FIELDS ( AgencyId TravelId FlightDate ) WITH CORRESPONDING #( keys ) RESULT DATA(items).
  10. In the implementation of the flight date validation, adjust the statements where you add messages to the reported parameter and fill the %path component.

    Caution

    Make sure you do not only fill the AgencyId and TravelId components of %path-travel, but also the %is_draft component!

    Hint

    You can use a CORRESPONDING expression to transfer all three fields from <item> to %path-travel.
    1. Adjust the APPEND statement in the IF branch as follows:

      Code Snippet
      1234567
      APPEND VALUE #( %tky = <item>-%tky %msg = NEW /lrn/cm_s4d437( /lrn/cm_s4d437=>field_empty ) %element-FlightDate = if_abap_behv=>mk-on %state_area = c_area %path-travel = CORRESPONDING #( <item> ) ) TO reported-item.
    2. Adjust the APPEND statement in the ELSEIF branch as follows:

      Code Snippet
      1234567
      APPEND VALUE #( %tky = <item>-%tky %msg = NEW /lrn/cm_s4d437( /lrn/cm_s4d437=>flight_date_past ) %element-FlightDate = if_abap_behv=>mk-on %state_area = c_area %path-travel = CORRESPONDING #( <item> ) ) TO reported-item.
  11. Activate the ABAP class and retest the preview for the OData UI service.

    1. Press Ctrl + F3 to activate the development object.

    2. Open the service binding, place the cursor on the root entity and choose Preview....

    3. In the preview, display the details for one of the travels, change to edit mode, and choose Create to create a new flight travel item.

    4. Leave all input fields empty, especially the Flight Date field, and choose Apply.

    5. Back on the object page of the travel, choose Save to trigger the execution of the validations.

      Result

      You see the (state) message with the link to the Flight Date input field.

Task 2: Read Data by Association

Extend the behavior definition of your business object. For the child entity Z##_R_TravelItem, define a determination to adjust the starting date and end date of a flight travel (suggested name: determineTravelDates) that is performed if the user changed the value of the FlightDate field. If the flight date is before the starting date set the starting date to that flight date. If the flight date is after the end date, set the end date to the flight date.

Steps

  1. Edit the behavior definition on data model level Z##_R_TRAVEL and add a determination for the child entity (suggested name: determineTravelDates) that is triggered by changes to the FlightDate field.

    1. Adjust the code as follows:

      Code Snippet
      12345678
      validation validateFlightDate on save { create; field FlightDate; } determination determineTravelDates on save { field FlightDate; }
  2. Activate the behavior definition and use a quick fix to generate the implementation method for the determination in the local handler class for flight travel items.

    1. Press Ctrl + F3 to activate the development object.

    2. Place the cursor on the name of the determination and press Ctrl + 1.

    3. From the list of available quick fixes, choose Add method for determination determinetraveldates of entity z##_r_travelitem in local class lhc_item.

  3. In the handler method for the determination, implement a READ ENTITIES statement to read the FlightDate field for all affected flight travel items. Store the result in an inline declared internal table (suggested name: items).

    1. Between METHOD determineTravelDates. and ENDMETHOD., add the following code:

      Code Snippet
      12345
      READ ENTITIES OF Z##_R_Travel IN LOCAL MODE ENTITY Item FIELDS ( FlightDate ) WITH CORRESPONDING #( keys ) RESULT DATA(items).
  4. In the same READ ENTITIES statement, add the BY keyword followed by a backslash and the name of the to parent association.

    1. Adjust the code as follows:

      Code Snippet
      12345678
      READ ENTITIES OF Z##_R_Travel IN LOCAL MODE ENTITY Item FIELDS ( FlightDate ) WITH CORRESPONDING #( keys ) RESULT DATA(items) BY \_Travel .
  5. Read the BeginDate and EndDate fields and store the result in an inline declared internal table (suggested name: travels) and the link information in another inline declared data object (suggested name: link).

    1. Adjust the code as follows:

      Code Snippet
      1234567891011
      READ ENTITIES OF Z##_R_Travel IN LOCAL MODE ENTITY Item FIELDS ( FlightDate ) WITH CORRESPONDING #( keys ) RESULT DATA(items). BY \_Travel FIELDS ( BeginDate EndDate ) WITH CORRESPONDING #( keys ) RESULT DATA(travels) LINK DATA(link).
  6. Implement a loop over the flight travel items, assigning an inline declared field symbol (suggested name: <item>). At the beginning of the loop, assign a field symbol to the entry of travels, that belongs to the current flight travel item (suggested name for the field symbol: <travel>).

    Hint

    Use the content of the link table to identify the related data. The link table contains pairs of source and target structures, where source contains the %tky key of a flight travel item and target contains the %tky key of the related flight travel.
    1. After the READ ENTITIES statement, add the following code:

      Code Snippet
      1234567
      LOOP AT items ASSIGNING FIELD-SYMBOL(<item>). ASSIGN travels[ %tky = link[ source-%tky = <item>-%tky ]-target-%tky ] TO FIELD-SYMBOL(<travel>). ENDLOOP.
    2. If you prefer a READ TABLE statement, the code inside the loop could look as follows:

      Code Snippet
      12
      READ TABLE travels ASSIGNING FIELD-SYMBOL(<travel>) WITH KEY %tky = link[ source-%tky = <item>-%tky ]-target-%tky.
    3. If you are familiar with secondary keys and want to get rid of the syntax warnings, you can reference the secondary keys as follows:

      Code Snippet
      123
      ASSIGN travels[ KEY id %tky = link[ KEY id source-%tky = <item>-%tky ]-target-%tky ] TO FIELD-SYMBOL(<travel>).
  7. Inside the loop, compare the flight date of the travel item to the end date of the travel. If the flight date is after the end date, use the field symbol to adjust the end date of the flight travel.

    1. Before the ENDLOOP statement, add the following code:

      Code Snippet
      123
      IF <travel>-EndDate < <item>-FlightDate. <travel>-EndDate = <item>-FlightDate. ENDIF.
  8. If the flight date is not in the past and lies before the starting date, use the field symbol to adjust the starting date of the flight travel.

    1. Before the ENDLOOP statement, add the following code:

      Code Snippet
      1234
      IF <item>-FlightDate > cl_abap_context_info=>get_system_date( ) AND <item>-FlightDate < <travel>-BeginDate. <travel>-BeginDate = <item>-FlightDate. ENDIF.
  9. After the LOOP, implement an EML statement to update the BeginDate and EndDate values of the flight travels.

    1. Between ENDLOOP. and ENDMETHOD., add the following code:

      Code Snippet
      12345
      MODIFY ENTITIES OF Z##_R_Travel IN LOCAL MODE ENTITY Travel UPDATE FIELDS ( BeginDate EndDate ) WITH CORRESPONDING #( travels ).
  10. Activate the ABAP class and retest the preview for the OData UI service.

    1. Press Ctrl + F3 to activate the development object.

    2. Open the service binding, place the cursor on the root entity and choose Preview....

    3. In the preview, display the details for one of the travels, change to edit mode, and choose Create to create a new flight travel item.

    4. Enter a value in the Flight Date field that lies after the end date of the travel.

    5. Back on the object page of the travel, choose Save to trigger the execution of the validations and determinations.

      Result

      You see the adjusted value in the End Date field.