RAP Deep Insert/Update - Saving Nested Entities

Category
RAP
Published
Author
Johannes

Deep Insert and Deep Update enable you to save parent and child entities in a single request. This is essential for complex business objects with composition relationships – such as an order with items or a travel with bookings.

The Problem: Multiple Requests

Without deep operations, you must create parent and children separately:

" ❌ Without Deep Insert: Multiple requests needed
" 1. Create parent
MODIFY ENTITIES OF zi_travel
ENTITY Travel
CREATE FIELDS ( AgencyId CustomerId )
WITH VALUE #( ( %cid = 'TRAVEL_1' AgencyId = '000001' CustomerId = '000042' ) )
MAPPED DATA(mapped_travel).
COMMIT ENTITIES.
" 2. Wait for generated TravelId
DATA(lv_travel_id) = mapped_travel-travel[ %cid = 'TRAVEL_1' ]-TravelId.
" 3. Create children separately
MODIFY ENTITIES OF zi_travel
ENTITY Booking
CREATE FIELDS ( CarrierId FlightDate )
WITH VALUE #(
( TravelId = lv_travel_id BookingId = '0001' CarrierId = 'LH' FlightDate = '20260301' )
( TravelId = lv_travel_id BookingId = '0002' CarrierId = 'AA' FlightDate = '20260308' )
).
COMMIT ENTITIES.
" → 2 round-trips, no transaction safety across both steps

The Solution: Deep Insert

With Deep Insert, you create parent and children in one operation:

" ✅ Deep Insert: One request for everything
MODIFY ENTITIES OF zi_travel
ENTITY Travel
CREATE FIELDS ( AgencyId CustomerId BeginDate EndDate Description )
WITH VALUE #( (
%cid = 'TRAVEL_1'
AgencyId = '000001'
CustomerId = '000042'
BeginDate = '20260301'
EndDate = '20260315'
Description = 'Business trip with flights'
) )
" Create children via association
ENTITY Travel CREATE BY \_Bookings
FIELDS ( CarrierId FlightDate FlightPrice CurrencyCode )
WITH VALUE #( (
%cid_ref = 'TRAVEL_1' " Reference to parent %cid
%target = VALUE #(
( %cid = 'BOOKING_1'
CarrierId = 'LH'
FlightDate = '20260301'
FlightPrice = '599.00'
CurrencyCode = 'EUR' )
( %cid = 'BOOKING_2'
CarrierId = 'AA'
FlightDate = '20260315'
FlightPrice = '749.00'
CurrencyCode = 'EUR' )
)
) )
MAPPED DATA(mapped)
FAILED DATA(failed)
REPORTED DATA(reported).
COMMIT ENTITIES
RESPONSE OF zi_travel
FAILED DATA(commit_failed)
REPORTED DATA(commit_reported).
" Retrieve all generated keys:
DATA(lv_travel_id) = mapped-travel[ %cid = 'TRAVEL_1' ]-TravelId.
DATA(lv_booking1_id) = mapped-booking[ %cid = 'BOOKING_1' ]-BookingId.
DATA(lv_booking2_id) = mapped-booking[ %cid = 'BOOKING_2' ]-BookingId.

Composition Relationships in CDS Views

Deep operations only work with Composition relationships. These define a strong parent-child relationship with lifecycle dependency.

Root Entity (Parent)

@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Travel - Root Entity'
define root view entity ZI_Travel
as select from ztravel
composition [0..*] of ZI_Booking as _Bookings -- Composition!
{
key travel_id as TravelId,
agency_id as AgencyId,
customer_id as CustomerId,
begin_date as BeginDate,
end_date as EndDate,
@Semantics.amount.currencyCode: 'CurrencyCode'
total_price as TotalPrice,
@Semantics.currencyCode: true
currency_code as CurrencyCode,
description as Description,
status as Status,
@Semantics.systemDateTime.lastChangedAt: true
last_changed_at as LastChangedAt,
_Bookings -- Expose association
}

Child Entity

@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Booking - Child Entity'
define view entity ZI_Booking
as select from zbooking
association to parent ZI_Travel as _Travel -- Parent association
on $projection.TravelId = _Travel.TravelId
{
key travel_id as TravelId,
key booking_id as BookingId,
carrier_id as CarrierId,
flight_date as FlightDate,
@Semantics.amount.currencyCode: 'CurrencyCode'
flight_price as FlightPrice,
@Semantics.currencyCode: true
currency_code as CurrencyCode,
booking_status as BookingStatus,
@Semantics.systemDateTime.lastChangedAt: true
last_changed_at as LastChangedAt,
_Travel -- Expose parent association
}

Behavior Definition

managed implementation in class zbp_i_travel unique;
strict ( 2 );
define behavior for ZI_Travel alias Travel
persistent table ztravel
lock master
authorization master ( instance )
etag master LastChangedAt
{
create;
update;
delete;
field ( readonly ) TravelId;
field ( numbering : managed ) TravelId;
association _Bookings { create; } -- Enable deep create!
}
define behavior for ZI_Booking alias Booking
persistent table zbooking
lock dependent by _Travel
authorization dependent by _Travel
etag master LastChangedAt
{
update;
delete;
field ( readonly ) TravelId, BookingId;
field ( numbering : managed ) BookingId;
association _Travel;
}

Important: association _Bookings { create; } enables Deep Insert for the composition.

Composition vs. Association

AspectCompositionAssociation
RelationshipStrong dependency (part-whole)Loose reference
LifecycleChild deleted with parentIndependent
Deep OperationsSupportedNot supported
Locklock dependent byOwn lock
CDS Syntaxcomposition [0..*] ofassociation [0..*] to
ExampleOrder → ItemsOrder → Customer

Deep Update with %control

With Deep Update, you update parent and children together. The %control structure controls which fields are actually updated.

Simple Deep Update

" Deep Update: Change parent and children together
MODIFY ENTITIES OF zi_travel
" Update parent
ENTITY Travel
UPDATE FIELDS ( Description Status )
WITH VALUE #( (
TravelId = '00000001'
Description = 'Updated description'
Status = 'A'
) )
" Update children
ENTITY Booking
UPDATE FIELDS ( FlightPrice )
WITH VALUE #(
( TravelId = '00000001'
BookingId = '0001'
FlightPrice = '549.00' )
( TravelId = '00000001'
BookingId = '0002'
FlightPrice = '699.00' )
)
FAILED DATA(failed)
REPORTED DATA(reported).
COMMIT ENTITIES.

Update with %control Structure

The %control structure explicitly specifies which fields should be updated:

" Precise control with %control
MODIFY ENTITIES OF zi_travel
ENTITY Travel
UPDATE
WITH VALUE #( (
TravelId = '00000001'
Description = 'New description'
Status = 'A'
TotalPrice = '1500.00' " Will NOT be updated (see below)
" Only these fields will be changed:
%control = VALUE #(
Description = if_abap_behv=>mk-on
Status = if_abap_behv=>mk-on
" TotalPrice = if_abap_behv=>mk-off " implicit
)
) )
FAILED DATA(failed)
REPORTED DATA(reported).
COMMIT ENTITIES.
" → TotalPrice remains unchanged, even though a value was passed

UPDATE SET FIELDS vs. UPDATE FIELDS

" UPDATE FIELDS: Explicit field list
MODIFY ENTITIES OF zi_travel
ENTITY Travel
UPDATE FIELDS ( Description Status ) -- Only these fields
WITH VALUE #( ( TravelId = '00000001' Description = 'Text' Status = 'A' ) ).
" UPDATE SET FIELDS: Automatically all non-initial fields
DATA(ls_update) = VALUE zi_travel(
TravelId = '00000001'
Description = 'Text'
Status = 'A'
" BeginDate is initial → will NOT be updated
).
MODIFY ENTITIES OF zi_travel
ENTITY Travel
UPDATE SET FIELDS
WITH VALUE #( ( CORRESPONDING #( ls_update ) ) ).

Deep Update with Creating New Children

" Update existing parent AND add new children
MODIFY ENTITIES OF zi_travel
" Update parent
ENTITY Travel
UPDATE FIELDS ( Status )
WITH VALUE #( ( TravelId = '00000001' Status = 'P' ) )
" Add NEW bookings to existing travel
ENTITY Travel CREATE BY \_Bookings
FIELDS ( CarrierId FlightDate FlightPrice CurrencyCode )
WITH VALUE #( (
TravelId = '00000001' " Existing parent
%target = VALUE #(
( %cid = 'NEW_BOOK_1'
CarrierId = 'UA'
FlightDate = '20260320'
FlightPrice = '899.00'
CurrencyCode = 'EUR' )
)
) )
MAPPED DATA(mapped)
FAILED DATA(failed)
REPORTED DATA(reported).
COMMIT ENTITIES.
" New BookingId:
DATA(lv_new_booking) = mapped-booking[ %cid = 'NEW_BOOK_1' ]-BookingId.

Error Scenarios and Rollback

Validation with Deep Insert

CLASS lhc_booking DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS validateFlightDate FOR VALIDATE ON SAVE
IMPORTING keys FOR Booking~validateFlightDate.
ENDCLASS.
CLASS lhc_booking IMPLEMENTATION.
METHOD validateFlightDate.
" Read data
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Booking
FIELDS ( TravelId BookingId FlightDate )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_bookings).
" Load parent data for validation
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Booking BY \_Travel
FIELDS ( BeginDate EndDate )
WITH CORRESPONDING #( lt_bookings )
RESULT DATA(lt_travels).
LOOP AT lt_bookings INTO DATA(ls_booking).
" FlightDate must be within travel dates
DATA(ls_travel) = VALUE #( lt_travels[ TravelId = ls_booking-TravelId ] OPTIONAL ).
IF ls_travel IS NOT INITIAL.
IF ls_booking-FlightDate < ls_travel-BeginDate OR
ls_booking-FlightDate > ls_travel-EndDate.
APPEND VALUE #(
TravelId = ls_booking-TravelId
BookingId = ls_booking-BookingId
%fail-cause = if_abap_behv=>cause-unspecific
) TO failed-booking.
APPEND VALUE #(
TravelId = ls_booking-TravelId
BookingId = ls_booking-BookingId
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |Flight date must be between { ls_travel-BeginDate } and { ls_travel-EndDate }|
)
%element-FlightDate = if_abap_behv=>mk-on
) TO reported-booking.
ENDIF.
ENDIF.
ENDLOOP.
ENDMETHOD.
ENDCLASS.

Error Handling with Deep Operations

MODIFY ENTITIES OF zi_travel
ENTITY Travel
CREATE FIELDS ( AgencyId CustomerId BeginDate EndDate )
WITH VALUE #( (
%cid = 'TRAVEL_1'
AgencyId = '000001'
CustomerId = '000042'
BeginDate = '20260301'
EndDate = '20260315'
) )
ENTITY Travel CREATE BY \_Bookings
FIELDS ( CarrierId FlightDate )
WITH VALUE #( (
%cid_ref = 'TRAVEL_1'
%target = VALUE #(
( %cid = 'BOOK_1' CarrierId = 'LH' FlightDate = '20260401' ) " Outside!
)
) )
MAPPED DATA(mapped)
FAILED DATA(failed)
REPORTED DATA(reported).
" Check if children failed
IF failed-booking IS NOT INITIAL.
WRITE: / 'Errors in bookings:'.
LOOP AT reported-booking INTO DATA(ls_book_msg).
WRITE: / ls_book_msg-%msg->if_message~get_text( ).
ENDLOOP.
ENDIF.
" Check if parent failed
IF failed-travel IS NOT INITIAL.
WRITE: / 'Errors in travel:'.
LOOP AT reported-travel INTO DATA(ls_travel_msg).
WRITE: / ls_travel_msg-%msg->if_message~get_text( ).
ENDLOOP.
ENDIF.
" COMMIT only on success
IF failed-travel IS INITIAL AND failed-booking IS INITIAL.
COMMIT ENTITIES
RESPONSE OF zi_travel
FAILED DATA(commit_failed)
REPORTED DATA(commit_reported).
IF commit_failed-travel IS INITIAL AND commit_failed-booking IS INITIAL.
WRITE: / 'Successfully saved'.
ELSE.
WRITE: / 'Commit failed - transaction rolled back'.
ENDIF.
ELSE.
WRITE: / 'Validation failed - no commit'.
ENDIF.

Automatic Rollback

With deep operations: All or nothing. If one part of the operation fails, the entire transaction is rolled back.

" Scenario: Travel OK, but one booking invalid
MODIFY ENTITIES OF zi_travel
ENTITY Travel
CREATE FIELDS ( AgencyId CustomerId BeginDate EndDate )
WITH VALUE #( (
%cid = 'T1'
AgencyId = '000001'
CustomerId = '000042'
BeginDate = '20260301'
EndDate = '20260310'
) )
ENTITY Travel CREATE BY \_Bookings
WITH VALUE #( (
%cid_ref = 'T1'
%target = VALUE #(
( %cid = 'B1' CarrierId = 'LH' FlightDate = '20260305' ) " OK
( %cid = 'B2' CarrierId = 'AA' FlightDate = '20260401' ) " Validation Error!
)
) )
MAPPED DATA(mapped)
FAILED DATA(failed)
REPORTED DATA(reported).
COMMIT ENTITIES
RESPONSE OF zi_travel
FAILED DATA(commit_failed).
" On error: NOTHING was saved!
" → Travel T1 was NOT created
" → Booking B1 was NOT created
" → Booking B2 was NOT created
" The entire transaction is rolled back.

Multi-Level Deep Insert

Deep operations also work across multiple levels:

" Travel → Booking → BookingSupplement (3 levels)
MODIFY ENTITIES OF zi_travel
" Level 1: Travel
ENTITY Travel
CREATE FIELDS ( AgencyId CustomerId BeginDate EndDate )
WITH VALUE #( ( %cid = 'T1' AgencyId = '000001' CustomerId = '000042'
BeginDate = '20260301' EndDate = '20260315' ) )
" Level 2: Bookings
ENTITY Travel CREATE BY \_Bookings
FIELDS ( CarrierId FlightDate FlightPrice CurrencyCode )
WITH VALUE #( (
%cid_ref = 'T1'
%target = VALUE #(
( %cid = 'B1' CarrierId = 'LH' FlightDate = '20260301'
FlightPrice = '599.00' CurrencyCode = 'EUR' )
)
) )
" Level 3: Booking Supplements
ENTITY Booking CREATE BY \_BookingSupplements
FIELDS ( SupplementId Price CurrencyCode )
WITH VALUE #( (
%cid_ref = 'B1'
%target = VALUE #(
( %cid = 'S1' SupplementId = 'ML01' Price = '29.00' CurrencyCode = 'EUR' )
( %cid = 'S2' SupplementId = 'BG01' Price = '49.00' CurrencyCode = 'EUR' )
)
) )
MAPPED DATA(mapped)
FAILED DATA(failed)
REPORTED DATA(reported).
COMMIT ENTITIES.

Best Practices

1. Use %cid and %cid_ref Consistently

" ✅ Unique, meaningful CIDs
%cid = 'ORDER_001'
%cid = 'ITEM_001_001'
%cid = 'ITEM_001_002'
" ❌ Avoid: Generic names
%cid = 'CID1'
%cid = 'X'

2. Always Implement Error Handling

" ✅ Always evaluate FAILED and REPORTED
MODIFY ENTITIES OF zi_travel ...
FAILED DATA(failed)
REPORTED DATA(reported).
IF failed-travel IS NOT INITIAL OR failed-booking IS NOT INITIAL.
" Error handling
ENDIF.

3. Validations at All Levels

" In BDEF: Validations for parent AND children
define behavior for ZI_Travel ...
{
validation validateDates on save { field BeginDate, EndDate; }
}
define behavior for ZI_Booking ...
{
validation validateFlightDate on save { field FlightDate; }
validation validateCarrier on save { field CarrierId; }
}

4. Performance with Large Data Volumes

" ✅ Bulk insert in one operation
MODIFY ENTITIES OF zi_travel
ENTITY Travel CREATE BY \_Bookings
WITH VALUE #(
FOR i = 1 UNTIL i > 100
( TravelId = '00000001'
%target = VALUE #(
( %cid = |BOOK_{ i }| CarrierId = 'LH' FlightDate = '20260301' + i )
)
)
).
" ❌ Avoid: Loop with individual inserts
LOOP AT lt_bookings INTO DATA(ls_booking).
MODIFY ENTITIES OF zi_travel
ENTITY Travel CREATE BY \_Bookings
WITH VALUE #( ( TravelId = '00000001' %target = VALUE #( ( ls_booking ) ) ) ).
ENDLOOP.

Additional Resources