SAP RAP Unmanaged Scenario with EML: Implement CUD on Standard BO

SAP RAP Unmanaged Scenario with EML: Implement CUD on Standard BO

SAP

In SAP RAP (Restful Application Programming Model), an unmanaged scenario gives developers full control over how data is read, modified, and saved in the database.

Unlike the managed approach, where SAP handles standard operations like Create, Read, Update, and Delete (CRUD) automatically, in unmanaged RAP, the ABAP developer must manually write the logic for these operations.

This approach is particularly useful in scenarios where:

  • You need to work with existing database tables.
  • Your application requires complex business logic.
  • You are integrating with legacy ABAP code.

Understanding SAP RAP’s Logical Unit of Work (LUW)

SAP RAP follows the SAP LUW (Logical Unit of Work) concept to ensure data consistency. The LUW consists of two main phases:

  • Transactional Buffer (Interaction Phase)
    • Acts as a temporary memory area where data changes are stored before being saved.
    • During this phase, data modifications happen in the buffer, which may be inconsistent.
    • The database remains unchanged until the final save sequence is triggered.
  • Save Sequence (Final Save Phase)
    • Ensures that all data changes are validated before saving them permanently.
    • If the data is consistent, it is committed to the database.
    • If validation fails, the process stops, and the data remains in the buffer for correction.

What we will be doing?

In this exercise, we will build an unmanaged scenario based RAP app to create and modify Bank records using the standard BNKA database table.

By the end of this exercise, our List Report Page and Object Page will look like this:

list report page unmanaged scenario

object page unmanaged scenario

We will be creating following objects –

  • Interface View
  • Projection View
  • Metadata Extension
  • Behavior Definition
  • Behavior Projection
  • Implementation Class
  • Service Definition
  • Service Binding

Unlike managed implementation based RAP app, we do not need to have a persistent database table here. So, we will use the standard BO - I_Bank_2 as the data source of our RAP application, which is a standard CDS view to show Bank details from the database table – BNKA.


Steps to create unmanaged scenario based RAP app

1) Interface View

Create a data definition with naming as - ZXXX_R_BANK, where XXX is your name initials. In my case, I am keeping the name - ZSAC_R_BANK.

From the list of templates, choose defineRootViewEntity to create a root CDS view -

rootview sachin

Use the code below and activate the CDS view:

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Bank root view'
@Metadata.ignorePropagatedAnnotations: true
define root view entity zsac_r_bank
  as select from I_Bank_2
{
  key BankCountry,
  key BankInternalID,
      BankName,
      BankBranch,
      SWIFTCode,
      BankNetworkGrouping,
      IsMarkedForDeletion,
      BankCategory,
      Bank
}

Code Explaination:

  1. We are fetching data from a standard Business Object I_Bank_2, which is based on the database table BNKA.
  2. Since our data model (ZSAC_R_BANK) is built on a standard Business Object, we cannot specify a persistent table in the Behavior Definition. As a result, we cannot use the managed implementation, because the RAP framework would not be able to automatically determine how CRUD operations should be handled.
  3. This is why we need to use the unmanaged scenario, where we manually define the CRUD operations.

The data model defines entities in a business scenario (e.g., TRAVEL and BOOKING) and their relationships. For example, a Material data model can fetch data from multiple tables like MARA, MAKT, and MARC.

2) Projection View

Create a data definition on top of the interface view with naming as - ZXXX_C_BANK.

From the list of templates, choose defineProjectionView to create a projection view -

projection view sachin

Use the code below and activate the CDS view:

@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Projection of bank'
@Metadata.ignorePropagatedAnnotations: true
@Metadata.allowExtensions: true
define root view entity zsac_c_bank
  provider contract transactional_query
  as projection on zsac_r_bank
{
  key BankCountry,
  key BankInternalID,
      BankName,
      BankBranch,
      SWIFTCode,
      BankNetworkGrouping,
      IsMarkedForDeletion,
      BankCategory,
      Bank
}

Code Explaination:

The keyword "provider contract transactional_query" ensures that the view follows RAP-specific rules and can be processed by both the RAP query engine and RAP transactional engine.

3) Metadata Extension

To display fields in the List Report, Object Page, and filters of the List Report, we need to add annotations in the Metadata Extension.

Since our projection view is used to expose data to the final application, it makes sense to create the Metadata Extension for the projection view itself, which in our case is ZSAC_C_BANK.

Create a Metadata Extension on top of the projection view with the same name pattern, such as ZXXX_C_BANK.

Use the code below and activate the Metadata Extension:

@Metadata.layer: #CORE
annotate entity zsac_c_bank with
{

  @UI.facet: [{
          id: 'Bank',
          purpose: #STANDARD,
          position: 10,
          label: 'Bank',
          type: #IDENTIFICATION_REFERENCE
      } ]

  @UI: { lineItem: [{ position:  10 }],
  identification: [{ position: 10 }],
  selectionField: [{ position: 10 }] }
  BankCountry;
  @UI: { lineItem: [{ position:  20 }],
  identification: [{ position: 20 }] }
  BankInternalID;
  @UI: { lineItem: [{ position:  30 }],
  identification: [{ position: 30 }],
  selectionField: [{ position: 20 }] }
  BankName;
  @UI: { lineItem: [{ position:  40 }],
  identification: [{ position: 40 }] }
  BankBranch;
  @UI: { lineItem: [{ position:  50 }],
  identification: [{ position: 50 }] }
  SWIFTCode;
  @UI: { lineItem: [{ position:  60 }],
  identification: [{ position: 60 }] }
  BankNetworkGrouping;
  @UI: { lineItem: [{ position:  70 }],
  identification: [{ position: 70 }] }
  IsMarkedForDeletion;
  @UI: { lineItem: [{ position:  80 }],
  identification: [{ position: 80 }] }
  BankCategory;
  @UI: { lineItem: [{ position:  90 }],
  identification: [{ position: 90 }] }
  Bank;

}

Code Explaination:

  1. Multiple Metadata Extensions can exist for a CDS view, each providing different annotations. The layer annotation determines priority, with #CUSTOMER having the highest and #CORE the lowest.
  2. @UI.lineItem → Displays fields as columns in the List Report.
  3. @UI.identification → Defines fields shown in the Object Page.
  4. @UI.selectionField → Adds fields as filters in the List Report.

4) Behavior Definition

To enable Create, Update, and Delete functionality in our data model, we need to create a Behavior Definition on top of the interface view. The Behavior Definition automatically takes the same name as the root interface view, so no need to name it separately.

Set the implementation type to Unmanaged and create the object -

behavior definition sachin

Copy and use the code below and activate it -

unmanaged implementation in class zbp_sac_r_bank unique;
strict ( 2 );

define behavior for zsac_r_bank
lock master
authorization master ( instance )
{
  create;
  update;
  delete;
  field ( mandatory : create, readonly : update ) BankCountry, BankInternalID;
  field ( readonly ) BankNetworkGrouping, IsMarkedForDeletion, Bank, BankCategory;
}

Code Explaination:

  1. The Create, Update, and Delete actions are implemented in the Behavior Implementation Class, which is auto-generated when creating the Behavior Definition (e.g., ZBP_SAC_R_BANK).

  2. Strict(2) mode ensures that the RAP Business Object remains lifecycle-stable and upgrade-safe.

  3. Since our data model has two keys (BankCountry and BankInternalID), we must enforce their input during record creation using:

    field ( mandatory:create ) → Ensures both fields must be entered when creating a new record.

    Key fields must remain unchanged after creation, so we use:
    field ( readonly:update ) → Makes key fields read-only when editing a record.

  4. To keep this exercise clean, we also set some non-key fields as read-only.

5) Behavior Projection

Create the Behavior Projection on top of the projection view. The Behavior Projection automatically takes the same name as the root projection view, so no need to name it separately.

Copy and use the code below and activate it -

projection;
strict ( 2 );

define behavior for zsac_c_bank
{
  use create;
  use update;
  use delete;
}

Code Explaination:

In Behavior Projection, we specify the actions to expose; for example, commenting out // use delete; removes the Delete functionality from the app.

6) Implementation Class

When you activated the Behavior Definition, you must have noticed a warning sign on line 1. This highlighted warning includes a quick-fix feature that allows you to create a class automatically. Simply click on the yellow warning icon and choose the only available option to create the class.

Once done, you will see two local classes being generated within the implementation class:

  • lhc_* (inherits from CL_ABAP_BEHAVIOR_HANDLER) – Handles transactional logic.
  • lsc_* (inherits from CL_ABAP_BEHAVIOR_SAVER) – Handles database operations.

Another key observation is that all methods in the handler class are generated based on the keywords used in the Behavior Definition. If you remove, say, the create keyword from the Behavior Definition and Behavior Projection, the class will throw an error until you remove the corresponding create method from the handler class.

Meanwhile, all methods in the saver class are inherited from CL_ABAP_BEHAVIOR_SAVER and are available for redefinition.

Enough theory! Now, let's copy the code below for the Create, Update, and Delete methods, then save and activate the class -

CLASS lhc_zsac_r_bank DEFINITION INHERITING FROM cl_abap_behavior_handler.
  PRIVATE SECTION.

    METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION
      IMPORTING keys REQUEST requested_authorizations FOR zsac_r_bank RESULT result.

    METHODS create FOR MODIFY
      IMPORTING entities FOR CREATE zsac_r_bank.

    METHODS update FOR MODIFY
      IMPORTING entities FOR UPDATE zsac_r_bank.

    METHODS delete FOR MODIFY
      IMPORTING keys FOR DELETE zsac_r_bank.

    METHODS read FOR READ
      IMPORTING keys FOR READ zsac_r_bank RESULT result.

    METHODS lock FOR LOCK
      IMPORTING keys FOR LOCK zsac_r_bank.

ENDCLASS.

CLASS lhc_zsac_r_bank IMPLEMENTATION.

  METHOD get_instance_authorizations.
  ENDMETHOD.

  METHOD create.

* get new record data from the transaction buffer
    DATA(ls_bank) = entities[ 1 ].

    DATA create_bank TYPE STRUCTURE FOR CREATE i_banktp.

    create_bank = VALUE #( bankcountry          = ls_bank-BankCountry
                           bankinternalid       = ls_bank-BankInternalID
                           longbankname         = ls_bank-BankName
                           longbankbranch       = ls_bank-BankBranch
                           banknumber           = ls_bank-bank
                           bankcategory = ''
                           banknetworkgrouping  = ls_bank-BankNetworkGrouping
                         ).

* Create a new bank record
    MODIFY ENTITIES OF i_banktp PRIVILEGED
        ENTITY bank
        CREATE FIELDS ( bankcountry
                        bankinternalid
                        longbankname
                        longbankbranch
                        banknumber
                        bankcategory
                        banknetworkgrouping
                      )
        WITH VALUE #( (
            %cid = 'cid1'
            bankcountry         = create_bank-bankcountry
            bankinternalid      = create_bank-bankinternalid
            longbankname        = create_bank-longbankname
            longbankbranch      = create_bank-longbankbranch
            banknumber          = create_bank-banknumber
            bankcategory        = create_bank-bankcategory
            banknetworkgrouping = create_bank-banknetworkgrouping
            )  )
       MAPPED DATA(mapped_bank) REPORTED DATA(reported_bank) FAILED DATA(failed_bank).

  ENDMETHOD.

  METHOD update.

* Changed record from transaction buffer
    DATA(ls_bank) = entities[ 1 ].

* Fetch existing data
    SELECT SINGLE * FROM zsac_r_bank
        WHERE BankCountry = @ls_bank-BankCountry
          AND BankInternalID = @ls_bank-BankInternalID
        INTO @DATA(ls_existing_bank).

* Update the bank data
    MODIFY ENTITIES OF i_banktp PRIVILEGED ENTITY bank
     UPDATE FIELDS (
                     longbankname
                     longbankbranch
                     SWIFTCode
                   )
     WITH VALUE #( (
         %key-bankcountry    = ls_bank-BankCountry
         %key-bankinternalid = ls_bank-BankInternalID
         longbankname        = COND #( WHEN ls_bank-BankName IS INITIAL
                                       THEN ls_existing_bank-BankName ELSE ls_bank-BankName )
         longbankbranch      = COND #( WHEN ls_bank-BankBranch IS INITIAL
                                       THEN ls_existing_bank-BankBranch ELSE ls_bank-BankBranch )
         SWIFTCode           = COND #( WHEN ls_bank-SWIFTCode IS INITIAL
                                       THEN ls_existing_bank-SWIFTCode ELSE ls_bank-SWIFTCode )
                 ) )
      FAILED DATA(failed_bank) REPORTED DATA(reported_bank) MAPPED DATA(updated_mapped).

  ENDMETHOD.

  METHOD delete.

    APPEND VALUE #( %tky = keys[ 1 ]-%tky ) TO failed-zsac_r_bank.
    APPEND VALUE #( %tky = keys[ 1 ]-%tky
                    %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error
                                                  text = 'Delete functionality is not enabled' )
                   ) TO reported-zsac_r_bank.

  ENDMETHOD.

  METHOD read.
  ENDMETHOD.

  METHOD lock.
  ENDMETHOD.

ENDCLASS.

CLASS lsc_ZSAC_R_BANK DEFINITION INHERITING FROM cl_abap_behavior_saver.
  PROTECTED SECTION.

    METHODS finalize REDEFINITION.

    METHODS check_before_save REDEFINITION.

    METHODS save REDEFINITION.

    METHODS cleanup REDEFINITION.

    METHODS cleanup_finalize REDEFINITION.

ENDCLASS.

CLASS lsc_ZSAC_R_BANK IMPLEMENTATION.

  METHOD finalize.
  ENDMETHOD.

  METHOD check_before_save.
  ENDMETHOD.

  METHOD save.

  ENDMETHOD.

  METHOD cleanup.
  ENDMETHOD.

  METHOD cleanup_finalize.
  ENDMETHOD.

ENDCLASS.

Pretty long code, right?

Code Explaination:

  1. In the Create method, we use an EML statement to create a new bank record with the user’s input from the UI. The syntax follows the I_BankTP interface documentation.

  2. In the Update method, we first fetch the existing record, then use an EML statement to update only the specified fields while keeping the key unchanged.

  3. In the Delete method, we demonstrate how to throw an error message to the UI, informing the user that delete functionality is not enabled.

EML (Entity Manipulation Language) in SAP RAP is a framework-specific language used to perform CRUD operations on RAP Business Objects. It enables interaction with managed and unmanaged RAP BOs while ensuring consistency and transactional integrity.

7) Service Definition

Since we need to expose the projection view to the final application, create a Service Definition on top of the projection view ZSAC_C_BANK, naming it ZSAC_UI_BANK.

Copy the code below, activate the object, and proceed:

@EndUserText.label: 'Service Definition of Bank'
define service Zsac_ui_bank {
  expose zsac_c_bank as Bank;
}

8) Service Binding

Create a Service Binding on top of the Service Definition, selecting OData V2 - UI service. Activate and publish it.

Once completed, preview the entity and test all functionalities, you now have a working service!

Why didn't we implement the Read functionality if it's unmanaged?

Because we are using a standard Business Object (BO) as the data source, which already provides the read capability. In an unmanaged RAP scenario, Read operations are automatically handled when the data source supports it, such as a standard CDS view or a standard BO.


By now, you should have a fair understanding of how SAP RAP Unmanaged Scenario works, how to perform CRUD operations using EML, and how behavior definitions control business logic. You also explored key RAP concepts like the transactional buffer and save sequence.

This was just a starting point!

You can further enhance your application by adding validations, determinations, and additional actions.

For more SAP insights and tutorials, check out SAP Insight Hub, a comprehensive resource covering OData, Fiori, ABAP, and more.

Keep experimenting, and happy coding!

Tags: #sap#eml#rap