Deep Insert y Deep Update permiten guardar Parent- y Child-Entities en un solo request. Esto es esencial para Business Objects complejos con relaciones Composition, como un pedido con posiciones o un viaje con reservas.
El Problema: Múltiples Requests
Sin Deep Operations, debe crear Parent e hijos por separado:
" ❌ 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 SchritteLa Solución: Deep Insert
Con Deep Insert crea Parent e hijos en una operación:
" ✅ 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.Relaciones Composition en CDS Views
Las Deep Operations solo funcionan con relaciones Composition. Estas definen una relación fuerte Parent-Child con dependencia de ciclo de vida.
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;}Importante: association _Bookings { create; } activa Deep Insert para la Composition.
Composition vs. Association
| Aspecto | Composition | Association |
|---|---|---|
| Relación | Dependencia fuerte (Parte-Todo) | Referencia suelta |
| Ciclo de vida | Child se elimina con Parent | Independiente |
| Deep Operations | Soportado | No soportado |
| Lock | lock dependent by | Lock propio |
| Sintaxis CDS | composition [0..*] of | association [0..*] to |
| Ejemplo | Pedido -> Posiciones | Pedido -> Cliente |
Deep Update con %control
Con Deep Update actualiza Parent e hijos juntos. La estructura %control controla qué campos se actualizan realmente.
Deep Update Simple
" 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 con Estructura %control
La estructura %control indica explícitamente qué campos deben actualizarse:
" 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 con Creación de Nuevos Children
" 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.Escenarios de Error y Rollback
Validation en 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.Manejo de Errores en 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.Rollback Automático
En Deep Operations aplica: Todo o nada. Si una parte de la operación falla, toda la transacción se revierte.
" 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.Deep Insert Multi-Nivel
Las Deep Operations también funcionan sobre múltiples niveles:
" 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.Mejores Prácticas
1. Usar %cid y %cid_ref Consistentemente
" ✅ Eindeutige, sprechende CIDs%cid = 'ORDER_001'%cid = 'ITEM_001_001'%cid = 'ITEM_001_002'
" ❌ Vermeiden: Generische Namen%cid = 'CID1'%cid = 'X'2. Siempre Implementar Manejo de Errores
" ✅ 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. Validaciones en Todos los Niveles
" 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. Rendimiento con Grandes Volúmenes de Datos
" ✅ 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.Recursos Adicionales
- Fundamentos EML: Entity Manipulation Language
- Fundamentos RAP: RAP Basics
- Managed vs Unmanaged: RAP Managed vs. Unmanaged
- Validaciones: RAP Validations
- Draft Handling: RAP Draft Handling