Deep Insert und Deep Update ermöglichen es, Parent- und Child-Entities in einem einzigen Request zu speichern. Dies ist essentiell für komplexe Business Objects mit Composition-Beziehungen – etwa eine Bestellung mit Positionen oder eine Reise mit Buchungen.
Das Problem: Mehrere Requests
Ohne Deep Operations müssen Sie Parent und Children separat anlegen:
" ❌ Ohne Deep Insert: Mehrere Requests nötig" 1. Parent anlegenMODIFY 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. Warten auf generierte TravelIdDATA(lv_travel_id) = mapped_travel-travel[ %cid = 'TRAVEL_1' ]-TravelId.
" 3. Children separat anlegenMODIFY 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, keine Transaktionssicherheit über beide SchritteDie Lösung: Deep Insert
Mit Deep Insert erstellen Sie Parent und Children in einer Operation:
" ✅ Deep Insert: Ein Request für allesMODIFY 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 = 'Geschäftsreise mit Flügen' ) )
" Children über Assoziation erstellen ENTITY Travel CREATE BY \_Bookings FIELDS ( CarrierId FlightDate FlightPrice CurrencyCode ) WITH VALUE #( ( %cid_ref = 'TRAVEL_1' " Referenz auf 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).
" Alle generierten Keys abrufen: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-Beziehungen in CDS Views
Deep Operations funktionieren nur mit Composition-Beziehungen. Diese definieren eine starke Parent-Child-Beziehung mit Lebenszyklusabhängigkeit.
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 -- Assoziation exponieren}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-Assoziation 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 -- Parent-Assoziation exponieren}Behavior Definition
managed implementation in class zbp_i_travel unique;strict ( 2 );
define behavior for ZI_Travel alias Travelpersistent table ztravellock masterauthorization master ( instance )etag master LastChangedAt{ create; update; delete;
field ( readonly ) TravelId; field ( numbering : managed ) TravelId;
association _Bookings { create; } -- Deep Create aktivieren!}
define behavior for ZI_Booking alias Bookingpersistent table zbookinglock dependent by _Travelauthorization dependent by _Traveletag master LastChangedAt{ update; delete;
field ( readonly ) TravelId, BookingId; field ( numbering : managed ) BookingId;
association _Travel;}Wichtig: association _Bookings { create; } aktiviert Deep Insert für die Composition.
Composition vs. Association
| Aspekt | Composition | Association |
|---|---|---|
| Beziehung | Starke Abhängigkeit (Teil-Ganzes) | Lose Referenz |
| Lebenszyklus | Child wird mit Parent gelöscht | Unabhängig |
| Deep Operations | Unterstützt | Nicht unterstützt |
| Lock | lock dependent by | Eigenes Lock |
| Syntax CDS | composition [0..*] of | association [0..*] to |
| Beispiel | Bestellung → Positionen | Bestellung → Kunde |
Deep Update mit %control
Bei Deep Update aktualisieren Sie Parent und Children gemeinsam. Die %control-Struktur steuert, welche Felder tatsächlich aktualisiert werden.
Einfaches Deep Update
" Deep Update: Parent und Children gemeinsam ändernMODIFY ENTITIES OF zi_travel " Parent aktualisieren ENTITY Travel UPDATE FIELDS ( Description Status ) WITH VALUE #( ( TravelId = '00000001' Description = 'Aktualisierte Beschreibung' Status = 'A' ) )
" Children aktualisieren 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 mit %control-Struktur
Die %control-Struktur gibt explizit an, welche Felder aktualisiert werden sollen:
" Präzise Kontrolle mit %controlMODIFY ENTITIES OF zi_travel ENTITY Travel UPDATE WITH VALUE #( ( TravelId = '00000001' Description = 'Neue Beschreibung' Status = 'A' TotalPrice = '1500.00' " Wird NICHT aktualisiert (s.u.)
" Nur diese Felder werden geändert: %control = VALUE #( Description = if_abap_behv=>mk-on Status = if_abap_behv=>mk-on " TotalPrice = if_abap_behv=>mk-off " implizit ) ) )
FAILED DATA(failed) REPORTED DATA(reported).
COMMIT ENTITIES." → TotalPrice bleibt unverändert, obwohl ein Wert übergeben wurdeUPDATE SET FIELDS vs. UPDATE FIELDS
" UPDATE FIELDS: Explizite FeldlisteMODIFY ENTITIES OF zi_travel ENTITY Travel UPDATE FIELDS ( Description Status ) -- Nur diese Felder WITH VALUE #( ( TravelId = '00000001' Description = 'Text' Status = 'A' ) ).
" UPDATE SET FIELDS: Automatisch alle non-initial FelderDATA(ls_update) = VALUE zi_travel( TravelId = '00000001' Description = 'Text' Status = 'A' " BeginDate ist initial → wird NICHT aktualisiert).
MODIFY ENTITIES OF zi_travel ENTITY Travel UPDATE SET FIELDS WITH VALUE #( ( CORRESPONDING #( ls_update ) ) ).Deep Update mit neuen Children erstellen
" Bestehenden Parent ändern UND neue Children hinzufügenMODIFY ENTITIES OF zi_travel " Parent aktualisieren ENTITY Travel UPDATE FIELDS ( Status ) WITH VALUE #( ( TravelId = '00000001' Status = 'P' ) )
" NEUE Bookings zum bestehenden Travel hinzufügen ENTITY Travel CREATE BY \_Bookings FIELDS ( CarrierId FlightDate FlightPrice CurrencyCode ) WITH VALUE #( ( TravelId = '00000001' " Existierender 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.
" Neue BookingId:DATA(lv_new_booking) = mapped-booking[ %cid = 'NEW_BOOK_1' ]-BookingId.Fehlerszenarien und Rollback
Validation bei 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. " Daten lesen READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Booking FIELDS ( TravelId BookingId FlightDate ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_bookings).
" Parent-Daten für Validierung laden 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 muss innerhalb der Reisedaten liegen 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 = |Flugdatum muss zwischen { ls_travel-BeginDate } und { ls_travel-EndDate } liegen| ) %element-FlightDate = if_abap_behv=>mk-on ) TO reported-booking. ENDIF. ENDIF. ENDLOOP. ENDMETHOD.ENDCLASS.Fehlerbehandlung bei 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' ) " Außerhalb! ) ) )
MAPPED DATA(mapped) FAILED DATA(failed) REPORTED DATA(reported).
" Prüfen, ob Children fehlgeschlagenIF failed-booking IS NOT INITIAL. WRITE: / 'Fehler bei Buchungen:'. LOOP AT reported-booking INTO DATA(ls_book_msg). WRITE: / ls_book_msg-%msg->if_message~get_text( ). ENDLOOP.ENDIF.
" Prüfen, ob Parent fehlgeschlagenIF failed-travel IS NOT INITIAL. WRITE: / 'Fehler bei Travel:'. LOOP AT reported-travel INTO DATA(ls_travel_msg). WRITE: / ls_travel_msg-%msg->if_message~get_text( ). ENDLOOP.ENDIF.
" COMMIT nur bei ErfolgIF 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: / 'Erfolgreich gespeichert'. ELSE. WRITE: / 'Commit fehlgeschlagen - Transaktion zurückgerollt'. ENDIF.ELSE. WRITE: / 'Validation fehlgeschlagen - kein Commit'.ENDIF.Automatischer Rollback
Bei Deep Operations gilt: Alles oder nichts. Wenn ein Teil der Operation fehlschlägt, wird die gesamte Transaktion zurückgerollt.
" Szenario: Travel OK, aber ein Booking ungültigMODIFY 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).
" Bei Fehler: NICHTS wurde gespeichert!" → Travel T1 wurde NICHT angelegt" → Booking B1 wurde NICHT angelegt" → Booking B2 wurde NICHT angelegt" Die gesamte Transaktion ist zurückgerollt.Multi-Level Deep Insert
Deep Operations funktionieren auch über mehrere Ebenen:
" Travel → Booking → BookingSupplement (3 Ebenen)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. %cid und %cid_ref konsistent verwenden
" ✅ Eindeutige, sprechende CIDs%cid = 'ORDER_001'%cid = 'ITEM_001_001'%cid = 'ITEM_001_002'
" ❌ Vermeiden: Generische Namen%cid = 'CID1'%cid = 'X'2. Fehlerbehandlung immer implementieren
" ✅ Immer FAILED und REPORTED auswertenMODIFY ENTITIES OF zi_travel ... FAILED DATA(failed) REPORTED DATA(reported).
IF failed-travel IS NOT INITIAL OR failed-booking IS NOT INITIAL. " FehlerbehandlungENDIF.3. Validierungen auf allen Ebenen
" In BDEF: Validierungen für Parent UND Childrendefine 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 bei großen Datenmengen
" ✅ Bulk-Insert in einer OperationMODIFY 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 ) ) ) ).
" ❌ Vermeiden: Loop mit einzelnen InsertsLOOP 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.Weitere Ressourcen
- EML Grundlagen: Entity Manipulation Language
- RAP Grundlagen: RAP Basics
- Managed vs Unmanaged: RAP Managed vs. Unmanaged
- Validierungen: RAP Validations
- Draft Handling: RAP Draft Handling