Skip to Content
Technical Articles
Author's profile photo Shyam Vasani

How to develop Managed Business Object with Unmanaged Save functionality in ABAP RESTful application programming model


Most of the project development has now started using ABAP RESTful application programming model Capabilities as it provides immense in-built functionalities, which allows the developer to focus on real business logic.

But all the business cases cannot be realized using generic Managed Business Object functionality. Developer might require tweaking the standard OData behavior.

Choosing Unmanaged Business object will overload the developer with same burden of handling all functionalities from the scratch.

There comes the Managed Business Object with Unmanaged Save functionality at rescue.


Managed Business Object with Unmanaged Save allows us to utilize the Managed Behavior for all the Business Object Nodes where the business requirement can be achieved using combination of Standard Create/Update/Delete, determination, validation and actions.

For the Business Object Nodes where overriding the Standard Create/Update/Delete (Behavioral change on save operation) is required, we can opt for this option.

Note – Determination, Validation, actions, draft functionality, etc. will continue to work the way it works for normal Managed Business Object Node.

Following example will guide you through the steps required to implement this capability.


Details – 

In following example, I’ve created a Managed Business Object with 2 Nodes –

  • InspectionPlan (Managed Business Object with Unmanaged Save functionality<Parent Node>)
  • InspectionPlanChecks (Managed Business Object with Standard Managed behavior<Child Node>)

We have to perform following changes, in order to implement Managed Business Object with Unmanaged Save capabilities –

Step 1 – Add keyword with unmanaged save for all the nodes, where unmanaged behavior is intended.

Ensure persistence table is not specified, as unmanaged save implementation cannot have persistence table specified to it.

We can still continue using Draft table (in case of Draft enabled Business Object).

Code Snippet for Behavior Definition –

managed implementation in class zbp_i_blg_inspectionplantp unique;
with draft;

define behavior for ZI_BLG_INSPECTIONPLANTP alias InspectionPlan
// persistent table ZBLG_T_INSP_TSP
draft table zblg_d_insp_tsp
with unmanaged save
lock master
total etag LastChangeDateTime
authorization master ( instance )
etag master LocalLastChangedDateTime

  field ( readonly, numbering : managed ) InspectionPlanUUID;
  field ( readonly ) InspectionPlanId;
  field ( mandatory ) InspectionPlanName, InspectionPlanStartdate, InspectionPlanEnddate;

  association _InspectionPlanChecks { create; with draft; }

  determination setInspectionPlanID on save { create; }

  draft action Edit;
  draft action Activate;
  draft action Discard;
  draft action Resume;
  draft determine action Prepare;

  mapping for zblg_t_insp_tsp control ZBLG_X_INSP_TSP corresponding
    InspectionPlanUUID = uuid;
    InspectionPlanId = id;
    InspectionPlanName = name;
    InspectionPlanStartdate = startdate;
    InspectionPlanEnddate = enddate;
    InspectionPlanText = text;
    Purchasinggroup = purchasinggroup;
    MaterialGroup = materialgroup;
    DueDate = duedate;
    InspectionPlanIsdeleted = isdeleted;

define behavior for ZI_BLG_INSPECTIONPLANCHECKSTP alias InspectionPlanChecks
persistent table zblg_t_insp_chks
draft table zblg_d_insp_chks
lock dependent by _InspectionPlan
authorization dependent by _InspectionPlan
etag dependent by _InspectionPlan
  field ( readonly, numbering : managed ) InspectionPlanCheckUUID;
  field ( readonly ) InspectionPlanUUID;

  association _InspectionPlan { with draft; }

  mapping for ZBLG_T_INSP_CHKS corresponding
    InspectionPlanCheckUUID = uuid;
    InspectionPlanUUID = inspection_uuid;
    QCategory = q_category;
    InspectionPlanCheckText = text;
    InspectionPlanCheckEnabled = enabled;

Step 2 – Redefine method save_modified in Saver Local class implementation level.

Please note, this method contains separate parameters for all 3 – Create / Update / Delete operations.

In case any child node is also having the unmanaged save capability enabled, we can expect/receive that child node details under the same create/update/delete parameter via relevant associations.

Code Snippet for Behavior Class (Local Definition/Implementation) implementation –

CLASS lsc_zi_blg_inspectionplantp DEFINITION INHERITING FROM cl_abap_behavior_saver.


    METHODS save_modified REDEFINITION.


CLASS lsc_zi_blg_inspectionplantp IMPLEMENTATION.

  METHOD save_modified.
    "Data Declaration
    DATA : lt_inspectionplan TYPE STANDARD TABLE OF zblg_t_insp_tsp.
    DATA : lr_inspectionplan_uuid TYPE RANGE OF zblg_t_insp_tsp-uuid.
    DATA : lt_inspectionplan_controlflag TYPE STANDARD TABLE OF zblg_x_insp_tsp.
    DATA : lt_inspectionplan_upd TYPE STANDARD TABLE OF zblg_t_insp_tsp.
    "Implement Logic for all possible Save Operations - Create / Update / Delete
    IF create IS NOT INITIAL.
      "Populate the values
      lt_inspectionplan = CORRESPONDING #( create-inspectionplan MAPPING FROM ENTITY ).

      "Insert record into DB
      INSERT zblg_t_insp_tsp FROM TABLE lt_inspectionplan.

    IF update IS NOT INITIAL.
      "Populate the values
      lt_inspectionplan_upd = CORRESPONDING #( update-inspectionplan MAPPING FROM ENTITY ).
      lt_inspectionplan_controlflag = CORRESPONDING #( update-inspectionplan MAPPING FROM ENTITY USING CONTROL ).

      "Fill range value for all the updated Inspections
      lr_inspectionplan_uuid = VALUE #( FOR ls_inspectionplan_u IN update-inspectionplan
                                     (  sign = 'I' option = 'EQ' low = ls_inspectionplan_u-inspectionplanuuid )
      IF lr_inspectionplan_uuid IS NOT INITIAL.
        "Get Old DB Values
        SELECT *
        FROM zblg_t_insp_tsp
        INTO TABLE @DATA(lt_inspectionplan_old)
        WHERE uuid IN @lr_inspectionplan_uuid.

      "Prepare DB Table to update
      lt_inspectionplan = VALUE #(
                                    FOR x = 1 WHILE x <= lines( lt_inspectionplan_upd )
                                      ls_controlflag = VALUE #( lt_inspectionplan_controlflag[ x ] OPTIONAL )
                                      ls_inspectionplan_upd = VALUE #( lt_inspectionplan_upd[ x ] OPTIONAL )
                                      ls_inspectionplan_old = VALUE #( lt_inspectionplan_old[ uuid = ls_inspectionplan_upd-uuid ] OPTIONAL )
                                       "Do not update cannonical, semantical keys, administrative fields
                                        uuid = ls_inspectionplan_old-uuid
                                        id   = ls_inspectionplan_old-id
                                        createdbyuser = ls_inspectionplan_old-createdbyuser
                                        creationdatetime = ls_inspectionplan_old-creationdatetime

                                       "Update other columns, if found controlflag as X - else pass DB values
                                        name = COND #( WHEN ls_controlflag-name IS NOT INITIAL THEN ls_inspectionplan_upd-name ELSE ls_inspectionplan_old-name )
                                        startdate = COND #( WHEN ls_controlflag-startdate IS NOT INITIAL THEN ls_inspectionplan_upd-startdate ELSE ls_inspectionplan_old-startdate )
                                        enddate = COND #( WHEN ls_controlflag-enddate IS NOT INITIAL THEN ls_inspectionplan_upd-enddate ELSE ls_inspectionplan_old-enddate )
                                        text = COND #( WHEN ls_controlflag-text IS NOT INITIAL THEN ls_inspectionplan_upd-text ELSE ls_inspectionplan_old-text )
                                        materialgroup = COND #( WHEN ls_controlflag-materialgroup IS NOT INITIAL THEN ls_inspectionplan_upd-materialgroup ELSE ls_inspectionplan_old-materialgroup )
                                        duedate = COND #( WHEN ls_controlflag-duedate IS NOT INITIAL THEN ls_inspectionplan_upd-duedate ELSE ls_inspectionplan_old-duedate )
                                        lastchangedatetime = COND #( WHEN ls_controlflag-lastchangedatetime IS NOT INITIAL THEN ls_inspectionplan_upd-lastchangedatetime ELSE ls_inspectionplan_old-lastchangedatetime )
                                        lastchangedbyuser = COND #( WHEN ls_controlflag-lastchangedbyuser IS NOT INITIAL THEN ls_inspectionplan_upd-lastchangedbyuser ELSE ls_inspectionplan_old-lastchangedbyuser )
                                        lastchangedat = COND #( WHEN ls_controlflag-lastchangedat IS NOT INITIAL THEN ls_inspectionplan_upd-lastchangedat ELSE ls_inspectionplan_old-lastchangedat )

      "Update DB table
      MODIFY zblg_t_insp_tsp FROM TABLE lt_inspectionplan.

    IF delete IS NOT INITIAL.
      "Fill range value for all the deleted Inspections
      lr_inspectionplan_uuid = VALUE #( FOR ls_inspectionplan_d IN delete-inspectionplan
                                     (  sign = 'I' option = 'EQ' low = ls_inspectionplan_d-inspectionplanuuid )
      IF lr_inspectionplan_uuid IS NOT INITIAL.
        "Delete the record from DB
        DELETE FROM zblg_t_insp_tsp WHERE uuid IN lr_inspectionplan_uuid.


CLASS lhc_InspectionPlan DEFINITION INHERITING FROM cl_abap_behavior_handler.

    METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION
      IMPORTING keys REQUEST requested_authorizations FOR InspectionPlan RESULT result.
    METHODS setinspectionplanid FOR DETERMINE ON SAVE
      IMPORTING keys FOR inspectionplan~setinspectionplanid.



  METHOD get_instance_authorizations.

  METHOD setInspectionPlanID.
    DATA : lv_inspectionplanid TYPE zblg_t_insp_tsp-id.
    "For external Scenario
    ENTITY InspectionPlan
    FIELDS ( InspectionPlanId ) WITH CORRESPONDING #( keys )
    RESULT DATA(lt_inspectionplan)
    FAILED DATA(lt_failed).

    "Do not generate Id, if already provided
    IF lt_inspectionplan[ 1 ]-InspectionPlanId IS NOT INITIAL.

    "Get new number from number range
            nr_range_nr = '01'
            object      = 'ZBLG_INSP'
            quantity    = '1'
            number      = DATA(lv_nr_number) ).
      CATCH cx_number_ranges INTO DATA(lx_number_ranges). " TODO: variable is assigned but never used (AbapCleaner)
        " exit

    UNPACK lv_nr_number TO lv_inspectionplanid.

    LOOP AT lt_inspectionplan INTO DATA(ls_inspectionplan).
             ENTITY InspectionPlan
             FIELDS ( InspectionPlanId  )
             WITH VALUE #( ( %tky             = ls_inspectionplan-%tky
                             InspectionPlanId = lv_inspectionplanid ) )
             REPORTED DATA(lt_update_reported).



Step 3 – (Optional/Good to have) As a best practice, always try to implement mapping structure with control parameter at individual Node level. This will be very useful while we are trying to read the data from the save_modified method input parameters and populating the internal table for the purpose of updating the DB table.

You can use following keyword in order to refer entity level mapping at ABAP Layer –

Notes –

  1. Never use any Database Commit or rollback keywords inside save_modified method.
  2. Also ensure that the execution of save_modified is not failing, as this method will get executed towards end of save_and_execute phase.
  3. Determinations (if used) gets executed before execution of save_modified method and all the fields will be determined before the control reaches this method. Hence, there is no need to write any such logic twice, e.g., Semantic Key generation, etc.
  4. All the Semantic annotation will continue to behave the way they behave in case of Managed Business Object Node. e.g., CreationLog ( @Semantics.user.createdBy: true / @Semantics.systemDateTime.createdAt: true ), ChangeLog fields ( @Semantics.user.lastChangedBy: true / @Semantics.systemDateTime.lastChangedAt: true ). etc.



During User Input –

After Save –



In the current blog post, integration of Unmanaged Save functionality as part of Managed Business Object (ABAP RESTful application programming model) is covered.

For more understanding, please use the following references which have helped me in gaining knowledge on this feature and get motivated to write this blog post:

I would encourage you to read through other blog posts on such topics at:

You can post and answer questions on related topics at:

Please provide your feedback and ask questions in the comments section.


Shyam Vasani

Assigned Tags

      You must be Logged on to comment or reply to a post.
      Author's profile photo Ahamed Kalikul Jaman
      Ahamed Kalikul Jaman

      Hi Shyam,

      Thanks for the blog.

      The method "SAVE_MODIFIED" has changing parameter "reported". In our case, We don't want to modify entities as we have a custom logic which would post the data to on-prem system. If the posting fails we want to pass the message.

      How do we pass the values into "reported" parameter without modify statement. The simple move-corresponding statement does not seem to work.



      Author's profile photo Shyam Vasani
      Shyam Vasani
      Blog Post Author

      Hi Ahamed,


      You can directly populate the reported parameter with relevant messages(if any), e.g.,

            "Add reported message
      %key                     inputEntityData-%key
      %msg                     new_messageid       'SY'  "Any message class
      number   001                "Any message number
      severity if_abap_behv_message=>severity-error   "Type of message

      %element-sampleFieldif_abap_behv=>mk-on )
      TO reported-exampleEntity.




      Author's profile photo Ahamed Kalikul Jaman
      Ahamed Kalikul Jaman

      Hi Shyam,

      Thanks for your input. The parameter "Reported" displays the error/success message as expected.

      We also have a separate entity for returning parameter and want to send the response back with this entity. our structure look like this

      where all the above fields has to be filled to send back.

      Is it possible to use the reported parameter for the same.(since all are flag fields)



      Author's profile photo Shyam Vasani
      Shyam Vasani
      Blog Post Author

      Hi Ahamed,

      save_modified methods reported parameters will only contain the entity which are having managed with unmanaged save enabled.


      if you wish to return the reported parameters in specific entity, you have to add unmanaged save keyword in that entity bahvior node as well.

      But in that case if there are any create/update/delete operations already working and being used with managed behavior, you will end up reimplementing them as well in save_modified method.




      Author's profile photo Ahamed Kalikul Jaman
      Ahamed Kalikul Jaman

      Hi Shyam,

      Thanks for your reply.

      I have already added unmanaged save for all my child entities( Item, Attach, Return ) as well and the composition and associations to parent are already in place.

      is it possible to fill the response data in return entity.



      Author's profile photo David Hubac
      David Hubac



      Little improvement. If you have a huge entity like me (57 fields), you may not want to list all of them and check %control. I have written a generic approach, that will iterate through the fields of the entity:

      method update_entity.
          check i_entity is not initial.
          assign i_entity to field-symbol(<entity>).
          data(old_entity) = dao->get_entity_by_key( corresponding #( i_entity ) ).
          assign old_entity to field-symbol(<entity_to_update>).
          data update_struct type ref to cl_abap_structdescr.
          update_struct ?= cl_abap_structdescr=>describe_by_data( <entity>-%control ).
          loop at update_struct->components into data(field).
            if <entity>-%control-(field-name) = if_abap_behv=>mk-on.
              <entity_to_update>-(field-name) = <entity>-(field-name).
          dao->modify_entity( <entity_to_update> ).
      Author's profile photo Matias Miano
      Matias Miano

      I've done every line of code, but my problem is that I need to execute a BAPI and a COMMIT inside of it (I'm updating a particular value from LFA1) and a dump is appearing that I cannot use a COMMIT inside SAVE_MODIFIED method.

      What needs to be done?