Technology Blog Posts by Members
cancel
Showing results for 
Search instead for 
Did you mean: 

Introduction  

The Draft capability in SAP RAP allows users to create, edit, and save business data without immediately changing the active business data.    

This is what powers the familiar Draft behavior in Fiori Elements applications. For example, a user can start filling in a form, navigate away, and later return to continue working on it — all before the data is finally activated as an active business record.    

Under the hood, RAP stores draft instances in a separate draft persistence (draft table), which is managed by the RAP framework.    

This introduces an important consideration when implementing access control. DCL (Data Control Language) is defined on CDS views, not directly on database tables. Also, access restrictions defined for the active CDS view do not automatically restrict access to draft instances.    

To address this requirement, SAP RAP provides the Draft Query View — a CDS view built on top of the draft table and connected to the behavior definition using the query addition.    

Once the Draft Query View is assigned, RAP can use this CDS view when querying draft instances. This provides a CDS-based layer where access restrictions can be defined specifically for draft data.    

In this blog, I'll walk through building a Price Change Request application end-to-end and demonstrate how the Draft Query View can be used to control access to draft instances.    

Why Does Draft Data Need Special Handling?    

A Draft Query View is a CDS view that reads data from the draft persistence.    

By default, DCL access restrictions defined for the active CDS view entity do not automatically apply to draft data. Draft instances therefore require their own access-control consideration.    

By assigning a Draft Query View using the query addition in the behavior definition, RAP can use the CDS view when reading draft instances. This allows you to define purpose-built access restrictions for draft data, similar to how access control is defined for active data.   

Implementation steps 

Step 1 : Create  a Data Base Table 

@EndUserText.label : 'Price Change Request'
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zaf_dt_pricerequ {

  key client     : abap.clnt not null;
  key request_id : abap.char(10) not null;
  material       : abap.char(18);
  sales_org      : abap.char(4);
  customer       : abap.char(10);
  current_price  : abap.dec(15,2);
  new_price      : abap.dec(15,2);
  currency       : abap.cuky;
  valid_from     : abap.dats;
  reason         : abap.char(90);
  status         : abap.char(20);
  changed_at     : timestampl;
  changed_by     : abap.char(15);
  created_at     : timestampl;
  created_by     : abap.char(15);

}

Step 2 : Create root CDS view for table

@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Price Change Request'
@Metadata.ignorePropagatedAnnotations: true
define root view entity ZAF_I_PRICEREQU as select from zaf_dt_pricerequ
{
    key request_id as RequestId,
    material as Material,
    sales_org as SalesOrg,
    customer as Customer,
    current_price as CurrentPrice,
    new_price as NewPrice,
    currency as Currency,
    valid_from as ValidFrom,
    reason as Reason,
    status as Status,
    @Semantics.systemDateTime.lastChangedAt: true
    changed_at as ChangedAt,
    changed_by as ChangedBy,
    created_at as CreatedAt,
    created_by as CreatedBy
}

Step 3: create projection view

@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Projection View'
@Metadata.ignorePropagatedAnnotations: true
@Metadata.allowExtensions: true
define root view entity ZAF_C_PRICEREQDF 
provider contract transactional_query
as projection on ZAF_I_PRICEREQU
{
    key RequestId,
    Material,
    SalesOrg,
    Customer,
    CurrentPrice,
    NewPrice,
    Currency,
    ValidFrom,
    Reason,
    Status,
    ChangedAt,
    ChangedBy,
    CreatedAt,
    CreatedBy
}

Step 4: Create the Projection Behavior Definition

projection;
strict ( 2 );
use draft;

define behavior for ZAF_C_PRICEREQDF 
{
  use create;
  use update;
  use delete;

  use action Activate;
  use action Discard;
  use action Edit;
  use action Resume;
  use action Prepare;
}


Step 5: Create Metadata Extension

@Metadata.layer: #CORE
annotate entity ZAF_C_PRICEREQDF
  with
{
  @UI.facet: [
    {
      id: 'PriceRequestHeader',
      purpose: #STANDARD,
      type: #IDENTIFICATION_REFERENCE,
      label: 'Price Change Request Details',
      position: 10
    }
  ]

  @UI.lineItem: [ { position: 10, label: 'Request ID' } ]
  @UI.identification: [ { position: 10, label: 'Request ID' } ]
  RequestId;

  @UI.lineItem: [ { position: 20, label: 'Material' } ]
  @UI.identification: [ { position: 20, label: 'Material' } ]
  Material;

  @UI.lineItem: [ { position: 30, label: 'Sales Organization' } ]
  @UI.identification: [ { position: 30, label: 'Sales Organization' } ]
  SalesOrg;

  @UI.lineItem: [ { position: 40, label: 'Customer' } ]
  @UI.identification: [ { position: 40, label: 'Customer' } ]
  Customer;

  @UI.lineItem: [ { position: 50, label: 'Current Price' } ]
  @UI.identification: [ { position: 50, label: 'Current Price' } ]
  CurrentPrice;

  @UI.lineItem: [ { position: 60, label: 'New Price' } ]
  @UI.identification: [ { position: 60, label: 'New Price' } ]
  NewPrice;

  @UI.lineItem: [ { position: 70, label: 'Currency' } ]
  @UI.identification: [ { position: 70, label: 'Currency' } ]
  Currency;

  @UI.lineItem: [ { position: 80, label: 'Valid From' } ]
  @UI.identification: [ { position: 80, label: 'Valid From' } ]
  ValidFrom;

  @UI.lineItem: [ { position: 90, label: 'Status' } ]
  @UI.identification: [ { position: 90, label: 'Status' } ]
  Status;

  @UI.identification: [ { position: 100, label: 'Reason' } ]
  Reason;

  @UI.hidden: true
  ChangedAt;
  @UI.hidden: true
  ChangedBy;
  @UI.hidden: true
  CreatedAt;
  @UI.hidden: true
  CreatedBy;
}

Step 6:  Create Behavior Definition 

  • Create the Draft Table.  
  • After creating the Draft Table, create an Interface CDS View on top of the Draft Table.  
  • In the behavior definition, declare the Draft Table using the query keyword and provide the name of the Interface CDS View created on top of the Draft Table. 

image.png

A few important points about this definition:  

  • Draft table zaf_df_pricedf query ZAF_I_PRICEREQUDF is the key line for this entire blog — it declares the draft table AND assigns the draft query view that will secure it. 

Step 7: Create Draft Data Base Table

@EndUserText.label : 'Draft table for entity ZAF_I_PRICEREQU'
@AbapCatalog.enhancement.category : #EXTENSIBLE_ANY
@AbapCatalog.tableCategory : #TRANSPARENT
@AbapCatalog.deliveryClass : #A
@AbapCatalog.dataMaintenance : #RESTRICTED
define table zaf_df_pricedf {

  key mandt     : mandt not null;
  key requestid : abap.char(10) not null;
  material      : abap.char(18);
  salesorg      : abap.char(4);
  customer      : abap.char(10);
  currentprice  : abap.dec(15,2);
  newprice      : abap.dec(15,2);
  currency      : abap.cuky;
  validfrom     : abap.dats;
  reason        : abap.char(90);
  status        : abap.char(20);
  changedat     : timestampl;
  changedby     : abap.char(15);
  createdat     : timestampl;
  createdby     : abap.char(15);
  "%admin"      : include sych_bdl_draft_admin_inc;

}

Step 8: Interface view of Draft data base table

@AbapCatalog.viewEnhancementCategory: [#NONE]
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'View on draft table'
@Metadata.ignorePropagatedAnnotations: true
define view entity ZAF_I_PRICEREQUDF
  as select from zaf_df_pricedf
  association [0..1] to I_DraftAdministrativeData as _DraftAdminData
    on $projection.DraftAdministrativeDataUUID = _DraftAdminData.DraftUUID
{
    key requestid                       as Requestid,
    material                            as Material,
    salesorg                            as Salesorg,
    customer                            as Customer,
    currentprice                        as Currentprice,
    newprice                            as Newprice,
    currency                            as Currency,
    validfrom                           as Validfrom,
    reason                              as Reason,
    status                              as Status,
    changedat                           as Changedat,
    changedby                           as Changedby,
    createdat                           as Createdat,
    createdby                           as Createdby,
    draftentitycreationdatetime         as Draftentitycreationdatetime,
    draftentitylastchangedatetime       as Draftentitylastchangedatetime,
    draftadministrativedatauuid         as DraftAdministrativeDataUUID,
    draftentityoperationcode            as Draftentityoperationcode,
    hasactiveentity                     as Hasactiveentity,
    draftfieldchanges                   as Draftfieldchanges,
    _DraftAdminData.InProcessByUser     as InProcessByUser,
    _DraftAdminData
}

Step 9: Create the DCL Roles

Two separate DCL roles are needed — one for the active view, one for the draft query view. 

Active data access control:

@EndUserText.label: 'Access control for price request active'
@MappingRole: true
define role ZAF_DCL_PRICEREQU {
    grant
        select
            on
                ZAF_I_PRICEREQU
                    where
                        Createdby = aspect user;

}

Draft data access control:

@EndUserText.label: 'Access control for price request draft'
@MappingRole: true
define role ZAF_DCL_PRICEREQU_DF {
    grant
        select
            on
                ZAF_I_PRICEREQUDF
               where
                        InProcessByUser = aspect user;
}

Notice these two roles filter on different fields — Createdby for active data, but InProcessByUser for draft data. This isn't arbitrary; it comes down to when each field actually gets populated.  

When a user clicks Create, RAP immediately and automatically inserts a row into the draft table — before any custom ABAP code runs. At that exact moment, the framework populates the draft admin data (including InProcessByUser, accessible via the association to I_DraftAdministrativeData), because that's system-managed data the framework itself owns.  

Createdby, on the other hand, is a business field — it only gets a value inside the handler class's create method, which only executes later, when Activate/Save is triggered. So immediately after Create, Createdby on the draft table is still blank, and filtering on it would silently hide every newly created draft.  

The rule of thumb: filter draft data using framework-populated draft admin fields, and filter active data using your own business fields — by the time something is active, your own logic has already run and populated them correctly. 

Step 10: Create the Behavior Handler and Saver Classes

CLASS lhc_ZAF_I_PRICEREQU DEFINITION
  INHERITING FROM cl_abap_behavior_handler.

  PRIVATE SECTION.

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

    METHODS create
      FOR MODIFY
      IMPORTING entities FOR CREATE zaf_i_pricerequ.

    METHODS earlynumbering_create
      FOR NUMBERING
      IMPORTING entities FOR CREATE zaf_i_pricerequ.

    METHODS update
      FOR MODIFY
      IMPORTING entities FOR UPDATE zaf_i_pricerequ.

    METHODS delete
      FOR MODIFY
      IMPORTING keys FOR DELETE zaf_i_pricerequ.

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

    METHODS lock
      FOR LOCK
      IMPORTING keys FOR LOCK zaf_i_pricerequ.

ENDCLASS.


CLASS lhc_ZAF_I_PRICEREQU IMPLEMENTATION.

  METHOD get_instance_authorizations.

    result = VALUE #(
      FOR key IN keys
      ( %tky    = key-%tky
        %update = if_abap_behv=>auth-allowed
        %delete = if_abap_behv=>auth-allowed ) ).

  ENDMETHOD.


  METHOD earlynumbering_create.

    DATA:
      lv_max_active TYPE i,
      lv_max_draft  TYPE i,
      lv_max_id     TYPE i,
      lv_new_id     TYPE zaf_dt_pricerequ-request_id.

    SELECT MAX( request_id )
      FROM zaf_dt_pricerequ
      INTO @DATA(lv_ext_active).

    IF lv_ext_active IS NOT INITIAL.
      lv_max_active = CONV i( lv_ext_active+2 ).
    ENDIF.

    SELECT MAX( requestid )
      FROM zaf_df_pricedf
      INTO @DATA(lv_ext_draft).

    IF lv_ext_draft IS NOT INITIAL.
      lv_max_draft = CONV i( lv_ext_draft+2 ).
    ENDIF.

    lv_max_id = nmax( val1 = lv_max_active val2 = lv_max_draft ).

    IF lv_max_id < 100010.
      lv_max_id = 100010.
    ENDIF.

    LOOP AT entities ASSIGNING FIELD-SYMBOL(<fs_entity>).

      IF <fs_entity>-RequestId IS NOT INITIAL.

        lv_new_id = <fs_entity>-RequestId.

      ELSE.

        WHILE 1 = 1.
          lv_max_id += 1.
          lv_new_id = |PR{ lv_max_id }|.

          SELECT SINGLE request_id FROM zaf_dt_pricerequ WHERE request_id = @LV_new_id INTO @DATA(lv_check_act).
          SELECT SINGLE requestid FROM zaf_df_pricedf WHERE requestid = @LV_new_id INTO @DATA(lv_check_dft).

          IF lv_check_act IS INITIAL AND lv_check_dft IS INITIAL.
            EXIT.
          ENDIF.
        ENDWHILE.

      ENDIF.

      APPEND VALUE #(
        %cid      = <fs_entity>-%cid
        RequestId = lv_new_id
        %is_draft = <fs_entity>-%is_draft
      ) TO mapped-zaf_i_pricerequ.

    ENDLOOP.

  ENDMETHOD.


  METHOD create.

    zcl_af_pricerequ=>get_instance( )->create_data(
      EXPORTING
        entities = entities
      CHANGING
        mapped   = mapped
        failed   = failed
        reported = reported ).

  ENDMETHOD.


  METHOD update.

    zcl_af_pricerequ=>get_instance( )->update_data(
      EXPORTING
        entities = entities
      CHANGING
        mapped   = mapped
        failed   = failed
        reported = reported ).

  ENDMETHOD.


  METHOD delete.

    zcl_af_pricerequ=>get_instance( )->delete_data(
      EXPORTING
        keys = keys
      CHANGING
        mapped   = mapped
        failed   = failed
        reported = reported ).

  ENDMETHOD.


  METHOD read.

    zcl_af_pricerequ=>get_instance( )->read_data(
      EXPORTING
        keys = keys
      CHANGING
        result   = result
        failed   = failed
        reported = reported ).

  ENDMETHOD.


METHOD lock.

ENDMETHOD.
ENDCLASS.


CLASS lsc_ZAF_I_PRICEREQU 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_ZAF_I_PRICEREQU IMPLEMENTATION.

  METHOD finalize.
  ENDMETHOD.

  METHOD check_before_save.
  ENDMETHOD.

  METHOD save.

    zcl_af_pricerequ=>get_instance( )->save_data( ).

  ENDMETHOD.

  METHOD cleanup.
  ENDMETHOD.

  METHOD cleanup_finalize.
  ENDMETHOD.

ENDCLASS.

Step 11: Create the Helper Class

CLASS zcl_af_pricerequ DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.

    TYPES:
      tt_create   TYPE TABLE FOR CREATE zaf_i_pricerequ,
      tt_update   TYPE TABLE FOR UPDATE zaf_i_pricerequ,
      tt_delete   TYPE TABLE FOR DELETE zaf_i_pricerequ,
      tt_read     TYPE TABLE FOR READ IMPORT zaf_i_pricerequ,
      tt_result   TYPE TABLE FOR READ RESULT zaf_i_pricerequ,

      tt_mapped   TYPE RESPONSE FOR MAPPED zaf_i_pricerequ,
      tt_failed   TYPE RESPONSE FOR FAILED zaf_i_pricerequ,
      tt_reported TYPE RESPONSE FOR REPORTED zaf_i_pricerequ.

    CLASS-METHODS get_instance
      RETURNING VALUE(ro_instance) TYPE REF TO zcl_af_pricerequ.

    METHODS create_data
      IMPORTING
        entities TYPE tt_create
      CHANGING
        mapped   TYPE tt_mapped
        failed   TYPE tt_failed
        reported TYPE tt_reported.

    METHODS update_data
      IMPORTING
        entities TYPE tt_update
      CHANGING
        mapped   TYPE tt_mapped
        failed   TYPE tt_failed
        reported TYPE tt_reported.

    METHODS delete_data
      IMPORTING
        keys     TYPE tt_delete
      CHANGING
        mapped   TYPE tt_mapped
        failed   TYPE tt_failed
        reported TYPE tt_reported.

    METHODS read_data
      IMPORTING
        keys     TYPE tt_read
      CHANGING
        result   TYPE tt_result
        failed   TYPE tt_failed
        reported TYPE tt_reported.

    METHODS save_data.

  PRIVATE SECTION.

    CLASS-DATA mo_instance TYPE REF TO zcl_af_pricerequ.

    DATA:
      mt_create TYPE STANDARD TABLE OF zaf_dt_pricerequ,
      mt_update TYPE STANDARD TABLE OF zaf_dt_pricerequ,
      mt_delete TYPE STANDARD TABLE OF zaf_dt_pricerequ.

ENDCLASS.


CLASS zcl_af_pricerequ IMPLEMENTATION.

  METHOD get_instance.

    IF mo_instance IS INITIAL.
      mo_instance = NEW zcl_af_pricerequ( ).
    ENDIF.

    ro_instance = mo_instance.

  ENDMETHOD.


  METHOD create_data.

    DATA:
      ls_db        TYPE zaf_dt_pricerequ,
      lv_timestamp TYPE timestampl.

    GET TIME STAMP FIELD lv_timestamp.

    LOOP AT entities ASSIGNING FIELD-SYMBOL(<fs_entity>).

      CLEAR ls_db.

      ls_db = VALUE #(
        client        = sy-mandt
        request_id    = <fs_entity>-RequestId
        material      = <fs_entity>-Material
        sales_org     = <fs_entity>-SalesOrg
        customer      = <fs_entity>-Customer
        current_price = <fs_entity>-CurrentPrice
        new_price     = <fs_entity>-NewPrice
        currency      = <fs_entity>-Currency
        valid_from    = <fs_entity>-ValidFrom
        reason        = <fs_entity>-Reason
        status        = 'NEW'
        changed_at    = lv_timestamp
        changed_by    = sy-uname
        created_at    = lv_timestamp
        created_by    = sy-uname
      ).

      APPEND ls_db TO mt_create.

      APPEND VALUE #(
        %cid      = <fs_entity>-%cid
        RequestId = <fs_entity>-RequestId
        %is_draft = <fs_entity>-%is_draft
      ) TO mapped-zaf_i_pricerequ.

    ENDLOOP.

  ENDMETHOD.


  METHOD update_data.

    DATA:
      ls_db        TYPE zaf_dt_pricerequ,
      lv_timestamp TYPE timestampl.

    GET TIME STAMP FIELD lv_timestamp.

    LOOP AT entities ASSIGNING FIELD-SYMBOL(<fs_entity>).

      SELECT SINGLE FROM zaf_dt_pricerequ
        FIELDS *
        WHERE request_id = @<fs_entity>-RequestId
        INTO @LS_db.

      IF sy-subrc <> 0.

        APPEND VALUE #(
          %key-RequestId = <fs_entity>-RequestId
        ) TO failed-zaf_i_pricerequ.

        CONTINUE.

      ENDIF.

      IF <fs_entity>-%control-Material = if_abap_behv=>mk-on.
        ls_db-material = <fs_entity>-Material.
      ENDIF.

      IF <fs_entity>-%control-SalesOrg = if_abap_behv=>mk-on.
        ls_db-sales_org = <fs_entity>-SalesOrg.
      ENDIF.

      IF <fs_entity>-%control-Customer = if_abap_behv=>mk-on.
        ls_db-customer = <fs_entity>-Customer.
      ENDIF.

      IF <fs_entity>-%control-CurrentPrice = if_abap_behv=>mk-on.
        ls_db-current_price = <fs_entity>-CurrentPrice.
      ENDIF.

      IF <fs_entity>-%control-NewPrice = if_abap_behv=>mk-on.
        ls_db-new_price = <fs_entity>-NewPrice.
      ENDIF.

      IF <fs_entity>-%control-Currency = if_abap_behv=>mk-on.
        ls_db-currency = <fs_entity>-Currency.
      ENDIF.

      IF <fs_entity>-%control-ValidFrom = if_abap_behv=>mk-on.
        ls_db-valid_from = <fs_entity>-ValidFrom.
      ENDIF.

      IF <fs_entity>-%control-Reason = if_abap_behv=>mk-on.
        ls_db-reason = <fs_entity>-Reason.
      ENDIF.

      IF <fs_entity>-%control-Status = if_abap_behv=>mk-on.
        ls_db-status = <fs_entity>-Status.
      ENDIF.

      ls_db-changed_at = lv_timestamp.
      ls_db-changed_by = sy-uname.

      APPEND ls_db TO mt_update.

    ENDLOOP.

  ENDMETHOD.


  METHOD delete_data.

    LOOP AT keys ASSIGNING FIELD-SYMBOL(<fs_key>).

      SELECT SINGLE request_id
        FROM zaf_dt_pricerequ
        WHERE request_id = @<fs_key>-RequestId
        INTO @DATA(lv_request_id).

      IF sy-subrc <> 0.

        APPEND VALUE #(
          %key-RequestId = <fs_key>-RequestId
        ) TO failed-zaf_i_pricerequ.

        CONTINUE.

      ENDIF.

      APPEND VALUE #(
        client     = sy-mandt
        request_id = <fs_key>-RequestId
      ) TO mt_delete.

    ENDLOOP.

  ENDMETHOD.


  METHOD read_data.

    IF keys IS INITIAL.
      RETURN.
    ENDIF.

    SELECT
      RequestId,
      Material,
      SalesOrg,
      Customer,
      CurrentPrice,
      NewPrice,
      Currency,
      ValidFrom,
      Reason,
      Status,
      ChangedAt,
      ChangedBy,
      CreatedAt,
      CreatedBy
      FROM zaf_i_pricerequ
      FOR ALL ENTRIES IN @Keys
      WHERE RequestId = @Keys-RequestId
      INTO CORRESPONDING FIELDS OF TABLE @result.

  ENDMETHOD.


  METHOD save_data.

    IF mt_create IS NOT INITIAL.
      INSERT zaf_dt_pricerequ FROM TABLE @MT_create.
    ENDIF.

    IF mt_update IS NOT INITIAL.
      UPDATE zaf_dt_pricerequ FROM TABLE @MT_update.
    ENDIF.

    IF mt_delete IS NOT INITIAL.
      DELETE zaf_dt_pricerequ FROM TABLE @MT_delete.
    ENDIF.

  ENDMETHOD.

ENDCLASS.

Step 12: Create the Service Definition

@EndUserText.label: 'Price Change Request UI Service'
define service ZAF_SD_PRICEREQU {
  expose ZAF_C_PRICEREQDF as PriceRequest;
}

Step 13 : Create the Service Binding

The Binding Type has to be ODATA V4 

Service Binding.png

Result

User A is trying to create the record 

image.png

Fig 2 — Create Form (Draft in progress): A new Price Change Request being filled in. Request ID PR100021 is already assigned via early numbering, and the "Draft updated" message confirms the entered fields are being persisted to the draft table before the final Create button is clicked. 

image.png

Fig 3 — Draft Table Data Preview: Confirming PR100021 is correctly persisted in the draft table (zaf_df_pricedf) with every field intact — proof that the draft query view and DCL setup allow this data to be read back correctly, rather than showing up blank as in the original bug. 

image.png

Now whenever other user (User B) tries to check for the record, which is in draft table, for that User B the draft table record will not be visible. 

image.png

But for the user (User A), the record in the draft table is visible. So only User A can make changes to the record which is in the draft table.  

image.png

 

Conclusion

This blog walked through implementing a draft-enabled RAP business object for a Price Change Request scenario, with particular focus on securing draft data using a Draft Query View. 

The key takeaway is that draft-enabled RAP objects work perfectly well without a draft query view — but doing so leaves draft data effectively unprotected by DCL, since access restrictions defined on the active CDS view entity do not automatically extend to draft instances. The query addition in the behavior definition closes this gap by giving draft reads their own dedicated CDS view, which can carry its own DCL role. 

A second, less obvious lesson from this implementation: filtering draft data by "ownership" needs to use framework-managed data (via an association to I_DraftAdministrativeData), not custom business fields like CreatedBy — because business fields are only populated once your own class logic runs, which happens later than the moment the draft row is actually created. 

Together, these two points are the core of what a Draft Query View is for, and why it matters beyond just being a syntax addition in the behavior definition. 

 

 

 

 

Labels in this area