0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【RAP】Unmanaged BOの実装 (2) Create処理

Last updated at Posted at 2022-02-15

はじめに

この記事では、前回の記事に続き、RAP(ABAP RESTful Application Programming Model)でUnmanaged BOを実装します。前回はAndre Fischer氏のRAP Generatorを使って骨組みを生成することろまで実施しました。今回はUnmanaged BOの肝であるBehavior Implementationクラスを実装していきます。

シリーズの先頭はこちら☞ 【RAP】Unmanaged BOの実装 (1)

生成されたクラスの定義を見てみる

まず、RAP Generatorによって生成されたクラスの定義を見てみます。PersonとFamilyMemberで2つのクラスができていますが、ここではPersonを見てみます。

クラスにはグローバルクラスとローカルタイプ(ローカルクラス)が設定されており、実際のコードを書くのはローカルクラスの部分になります。

グローバルクラス

class ZBP_I_PERSON_U definition
  public
  abstract
  final
  for behavior of ZI_PERSON_U .

public section.
protected section.
private section.
ENDCLASS.



CLASS ZBP_I_PERSON_U IMPLEMENTATION.
ENDCLASS.

ローカルクラス
ローカルクラスには2つのクラスの定義が入っています。一つはCL_ABAP_BEHAVIOR_HANDLERを継承したクラスで、トランザクションバッファとのやり取りを担います。もう一つはCL_ABAP_BEHAVIOR_SAVERを継承したクラスで、DBへの保存に伴う処理を行います。

ドラフトを使っているならバッファってドラフトテーブルになるのでは?」と思ったのですが、そうではありませんでした。ドラフト保存はInteraction Phaseよりも前に行われており、下図のやり取りが発生するのは「保存」ボタンを押した後でした。

image.png

CLASS LHC_PERSON DEFINITION INHERITING FROM CL_ABAP_BEHAVIOR_HANDLER.
  PRIVATE SECTION.
    METHODS:
      CREATE FOR MODIFY
        IMPORTING
          ENTITIES FOR CREATE  Person,
      UPDATE FOR MODIFY
        IMPORTING
          ENTITIES FOR UPDATE  Person,
      DELETE FOR MODIFY
        IMPORTING
          KEYS FOR DELETE  Person,
      LOCK FOR LOCK
        IMPORTING
          KEYS FOR LOCK  Person,
      READ FOR READ
        IMPORTING
          KEYS FOR READ  Person
        RESULT result,
      CBA_FAMILYMEMBER FOR MODIFY
        IMPORTING
          ENTITIES_CBA FOR CREATE  Person\_FamilyMember,
      RBA_FAMILYMEMBER FOR READ
        IMPORTING
          KEYS_RBA FOR READ  Person\_FamilyMember
        FULL result_requested
        RESULT result
        LINK association_links.
ENDCLASS.

CLASS LHC_PERSON IMPLEMENTATION.
  ...
ENDCLASS.
CLASS LCL_ZI_PERSON_U DEFINITION INHERITING FROM CL_ABAP_BEHAVIOR_SAVER.
  PROTECTED SECTION.
    METHODS:
      FINALIZE REDEFINITION,
      CHECK_BEFORE_SAVE REDEFINITION,
      SAVE REDEFINITION,
      CLEANUP REDEFINITION,
      CLEANUP_FINALIZE REDEFINITION.
ENDCLASS.

CLASS LCL_ZI_PERSON_U IMPLEMENTATION.
  ...
ENDCLASS.

各メソッドで実施すること

以下が各メソッドで実施することです。

クラス メソッド やること 備考
BEHAVIOR_HANDLER CREATE 受け取ったデータを登録用のバッファに格納する 必要に応じてエンティティのDBの項目マッピングや値の代入も行う
UPDATE 受け取ったデータを更新用のバッファに格納する 同上
DELETE 受け取ったデータを削除用のバッファに格納する
LOCK データベースのレコードに悲観ロックをかける 更新、削除、アクションで呼ばれる。ロックの解除はCLEANUP/CLEANUP_FINALIZEで実施する
READ バッファのデータを返す。バッファにデータがなければデータベースから読み込んで返す 一覧から"Go"を押したときには呼ばれない。UPDATEの際にETagを比較するときなどに使う
CBA_FAMILYMEMBER 受け取ったデータをFamilyMemberの登録用のバッファに格納する Create DeepでPersonとFamily Memberを登録するときに使用
RBA_FAMILYMEMBER FamilyMemberのバッファのデータを返す。バッファにデータがなければデータベースから読み込んで返す PersonとのアソシエーションでFamilyMemberを取得するときに使用
BEHAVIOR_SAVER FINALIZE データベースに保存する直前にバッファのデータを編集する
CHECK_BEFORE_SAVE データベースに保存する直前にバッファのデータの整合性をチェックする
SAVE バッファのデータをデータベースに保存する
CLEANUP 更新が完了した後、バッファのデータを初期化する。また、データベースの更新ロックを解除する
CLEANUP_FINALIZE FINALIZEまたはCHECK_BEFORE_SAVEでエラーになった場合にバッファのデータを初期化する。また、データベースの更新ロックを解除する

参考:Save Sequence Runtime

Adapterクラスの作成

Adapterクラスを作成し、Behavior Implementationクラスと役割を分担します。この考え方はSAP Pressの書籍ABAP RESTful Programming Modelを参考にしました。以下の図には、簡単にするためにLOCK, FINALIZE, CHECK_BEFORE_SAVEなどは入れていません。
image.png

以下が役割分担です。

Behavior Implementationクラス

  • エンティティの項目をDBが受け取る形式にマッピングする
  • Adapterクラスを呼び出してバッファへのデータ格納やDBへの保存を行う

Adapterクラス

  • チェックや値の代入などのビジネスロジックの適用
  • CREATE, UPDATE, DELETE処理ではハンドラクラスから受け取った値をバッファに格納する
  • SAVEでバッファからDBにデータを保存する
  • INITIALIZEでバッファを初期化する

上記ではSAVE処理が直接DBを更新していますが、BAPIなどを呼んで更新するケースもあると思います。BAPIを使うときに注意したいのは、SAVEでバリデーションエラーが発生しても画面にエラーは返せないので、CHECK_BEFORE_SAVEなどで事前にチェックをかけておく必要があるということです。

実装ステップ

今回はPersonとFamily Memberの登録、採番ができるところまでをゴールとします。

  1. Behavior Definitionにクラスをひもづける
  2. Adapterクラスの作成
  3. Behavior Implementationクラスの実装
  4. Late Numberingの実装

###1. Behavior Definitionにクラスをひもづける
Behavior Definitionの1行目で、以下のようにクラスを指定します。

unmanaged implementation in class zbp_i_person_u unique;

FamilyMemberの方も同様に...と思ったら、こちらはすでに指定されていました。

define behavior for ZI_FAMILYMEMBER_U alias FamilyMember
implementation in class ZBP_I_FamilyMember_u unique

2. Adapterクラスの作成

以下のようなクラスを新規に作成します。型の定義などはインターフェースで行うため、インターフェースを実装する形とします。

2.1. インターフェースの作成

以下のインターフェースを登録します。この時点では、バッファとして使うためのテーブル型のみ定義します。

INTERFACE zif_person_adapter
  PUBLIC .

  TYPES tt_person TYPE SORTED TABLE OF zperson_u WITH UNIQUE KEY person_id.
  TYPES tt_familymember TYPE SORTED TABLE OF zfamilymember_u WITH UNIQUE KEY person_id member_id.

ENDINTERFACE.

2.2. インターフェースを実装してクラスを作成

上記で作成したインターフェースを実装するクラスを登録します。

CLASS zcl_person_adapter DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.

    INTERFACES zif_person_adapter .
  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.



CLASS zcl_person_adapter IMPLEMENTATION.
ENDCLASS.

2.3. ローカルクラスの実装

作成したクラスのローカルクラス(Local Typsのタブ)に、バッファとのやり取りを行うためのローカルクラスを作成します。

クラス定義は以下のようになります。以下のメソッドを定義しています。

  • get_instance:インスタンスを取得する
  • buffer_person_for_create:Person登録用のバッファにデータを格納する
  • buffer_familymember_for_create:Family Member登録用のバッファにデータを格納する
  • save:バッファのデータをDBに登録する
  • initialize:バッファを初期化する
CLASS lcl_person_buffer DEFINITION FINAL CREATE PRIVATE.
  PUBLIC SECTION.
    " Buffer Tables
    DATA mt_create_buffer_person TYPE zif_person_adapter=>tt_person.
    DATA mt_create_buffer_familymember TYPE zif_person_adapter=>tt_familymember.

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

    METHODS buffer_person_for_create IMPORTING is_person TYPE zperson_u.
    METHODS buffer_familymember_for_create IMPORTING is_familymember TYPE zfamilymember_u.

    METHODS save.
    METHODS initialize.

  PRIVATE SECTION.
    CLASS-DATA go_instance TYPE REF TO lcl_person_buffer.


ENDCLASS.

以下がメソッドの実装です。この簡単な例だとイメージしづらいですが、実際にはこのローカルクラスが「レガシー」のコードを呼び、値の代入やチェックを行います。

CLASS lcl_person_buffer IMPLEMENTATION.
  METHOD get_instance.
    go_instance = COND #( WHEN go_instance IS BOUND THEN go_instance ELSE NEW #(  ) ).
    ro_instance = go_instance.
  ENDMETHOD.

  METHOD buffer_person_for_create.
    CHECK is_person IS NOT INITIAL.
    INSERT is_person INTO TABLE mt_create_buffer_person.
  ENDMETHOD.

  METHOD buffer_familymember_for_create.
    CHECK is_familymember IS NOT INITIAL.
    INSERT is_familymember INTO TABLE mt_create_buffer_familymember.
  ENDMETHOD.

  METHOD save.
    INSERT zperson_u FROM TABLE @mt_create_buffer_person.
    INSERT zfamilymember_u FROM TABLE @mt_create_buffer_familymember.
  ENDMETHOD.

  METHOD initialize.
    CLEAR: mt_create_buffer_person,
           mt_create_buffer_familymember.
  ENDMETHOD.

ENDCLASS.

2.4. グローバルクラスの実装

グローバルクラス(Behavior Implementationから直接呼ばれる処理)を実装します。処理はローカルクラスに任せるので、対応するローカルクラスを呼ぶだけです。

CLASS zcl_person_adapter DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.

    INTERFACES zif_person_adapter .

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

    METHODS create_person IMPORTING is_person TYPE zperson_u.
    METHODS create_familymember IMPORTING is_familymember TYPE zfamilymember_u.
    METHODS save.
    METHODS initialize.

  PROTECTED SECTION.
  PRIVATE SECTION.
    CLASS-DATA go_instance TYPE REF TO zcl_person_adapter.
ENDCLASS.



CLASS zcl_person_adapter IMPLEMENTATION.
  METHOD get_instance.
    go_instance = COND #( WHEN go_instance IS BOUND THEN go_instance ELSE NEW #(  ) ).
    ro_instance = go_instance.

  ENDMETHOD.

  METHOD create_person.
    lcl_person_buffer=>get_instance( )->buffer_person_for_create( is_person ).

  ENDMETHOD.

  METHOD create_familymember.
    lcl_person_buffer=>get_instance( )->buffer_familymember_for_create( is_familymember ).
  ENDMETHOD.

  METHOD save.
    lcl_person_buffer=>get_instance( )->save( ).
  ENDMETHOD.

  METHOD initialize.
    lcl_person_buffer=>get_instance( )->initialize(  ).
  ENDMETHOD.

ENDCLASS.

3. Behavior Implementationクラスの実装

createsave, initializeメソッドのみ実装します。createではエンティティの項目をDBの項目にマッピングし、アダプタークラスに渡しています。
APPING FROM ENTITYというステートメントは、Behavior Definitionで定義したマッピングの通りにエンティティの項目をDBの項目にマッピングしてくれます。USING CONTROLをつけると、%control構造にフラグがついた項目のみがマッピングされます。

CLASS LHC_PERSON IMPLEMENTATION.
  METHOD CREATE.
    data person_db type zperson_u.

    loop at entities ASSIGNING FIELD-SYMBOL(<person>).
      person_db = CORRESPONDING #( <person> MAPPING FROM ENTITY USING CONTROL ).
      zcl_person_adapter=>get_instance(  )->create_person( person_db ).
    ENDLOOP.

  ENDMETHOD.
  METHOD UPDATE.
  ENDMETHOD.
  METHOD DELETE.
  ENDMETHOD.
  METHOD LOCK.
  ENDMETHOD.
  METHOD READ.
    if sy-subrc = 0.
    endif.
  ENDMETHOD.
  METHOD CBA_FAMILYMEMBER.
    data familymember_db type zfamilymember_u.

    loop at ENTITIES_CBA ASSIGNING FIELD-SYMBOL(<person>).
      loop at <person>-%target ASSIGNING FIELD-SYMBOL(<member>).
        familymember_db = CORRESPONDING #( <member> mapping from entity using control ).
        zcl_person_adapter=>get_instance(  )->create_familymember( familymember_db ).
      ENDLOOP.
    ENDLOOP.
  ENDMETHOD.
  METHOD RBA_FAMILYMEMBER.
  ENDMETHOD.
ENDCLASS.
CLASS LCL_ZI_PERSON_U DEFINITION INHERITING FROM CL_ABAP_BEHAVIOR_SAVER.
  PROTECTED SECTION.
    METHODS:
      FINALIZE REDEFINITION,
      CHECK_BEFORE_SAVE REDEFINITION,
      SAVE REDEFINITION,
      CLEANUP REDEFINITION,
      CLEANUP_FINALIZE REDEFINITION.
ENDCLASS.

CLASS LCL_ZI_PERSON_U IMPLEMENTATION.
  METHOD FINALIZE.
  ENDMETHOD.
  METHOD CHECK_BEFORE_SAVE.
  ENDMETHOD.
  METHOD SAVE.
    zcl_person_adapter=>get_instance(  )->save(  ).
  ENDMETHOD.
  METHOD CLEANUP.
    zcl_person_adapter=>get_instance(  )->initialize( ).
  ENDMETHOD.
  METHOD CLEANUP_FINALIZE.
    zcl_person_adapter=>get_instance(  )->initialize( ).
  ENDMETHOD.
ENDCLASS.

動作確認(1)

ここまでのところで、とりあえずデータをDBに保存できるようになったので動作確認をしてみます。この時点では採番処理がないので、画面からマニュアルで番号を設定できるように、Behavior Definitionでキー項目のReadonly設定をコメントアウトします。

nmanaged implementation in class zbp_i_person_u unique;
with draft;

define behavior for ZI_PERSON_U alias Person
implementation in class ZBP_I_Person_u unique
draft table ZPERSON00D_U
etag master LocalLastChangedAt
lock master total etag LastChangedAt

{
//  field ( readonly ) コメントアウト
//   PersonID;

...
define behavior for ZI_FAMILYMEMBER_U alias FamilyMember
implementation in class ZBP_I_FamilyMember_u unique
draft table ZFAMILYMEMB00D_U
etag dependent by _Person
lock dependent by _Person

{
  field ( readonly )
   PersonID;
//   MemberID; コメントアウト

登録ボタンを押すと、Person IDを聞かれるので手で入力します。
image.png
必要な項目に入力し、Family Membersも登録します。
image.png
Member IDを手入力します。
image.png
入力が終わり"Create"ボタンを押すと、データが保存されました。
image.png

4. Late Numberingの実装

Person IDおよびMember IDをLate Numberingで採番するようにします。このためには、Saverクラスのadjust_numbersを実装します。
image.png
以前、以下の記事でManaged BOでのLate Numberingについて扱いました。
【RAP】Late Numbering
Managedの場合、adjust_numbersでやることは仮のキーと採番したキーのマッピングを返すことだけでした。Unmanagedの場合、これに加えて登録用のバッファに持っているキーを採番したキーで更新する必要があります。

処理概要

まず、createのタイミングで仮キーとなる%pidを採番し、%cidとのマッピングをmappedというパラメータに返します。次に、adjust_numbersで%pidと実際に採番されたキーのマッピングを返します。これにより、RAPのフレームワークがLate Numberingで採番したキーを登録されたエンティティと紐づけてくれます。
image.png

4.1. Behavior Definitionの変更

Behavior Definitionの定義にlate numberingを追加します。また、キーとなる項目(PersonID, MemberID)をreadonlyにします。
※MemberIDはEarly Numberingでやりたかったのですが、PersonIDをキーに含んでしまっているためLate Numberingしか指定できませんでした。

unmanaged implementation in class zbp_i_person_u unique;
with draft;

define behavior for ZI_PERSON_U alias Person
implementation in class ZBP_I_Person_u unique
draft table ZPERSON00D_U
etag master LocalLastChangedAt
lock master total etag LastChangedAt
late numbering //追加

{
  field ( readonly )
   PersonID;
...
define behavior for ZI_FAMILYMEMBER_U alias FamilyMember
implementation in class ZBP_I_FamilyMember_u unique
draft table ZFAMILYMEMB00D_U
etag dependent by _Person
lock dependent by _Person
late numbering //追加

{
  field ( readonly )
   PersonID,
   MemberID;

4.2 Create処理の変更

Late Numberingに対応させるため、create関連の処理に以下の変更を行います。

4.2.1 インターフェースに型を追加

createのバッファでpidを持つ必要があるため、インターフェースにcreate用の型を追加します。また、adjust_numbersで使用する、pidと実際のキーを紐づける型も追加します。

  "登録用
  TYPES: BEGIN OF ts_person_c,
           pid    TYPE sysuuid_x16,
           person TYPE zperson_u,
         END OF ts_person_c.

  TYPES: BEGIN OF ts_familymember_c,
           pid        TYPE sysuuid_x16,
           familymember TYPE zfamilymember_u,
         END OF ts_familymember_c.

  TYPES tt_person_c TYPE SORTED TABLE OF ts_person_c with UNIQUE key pid.
  TYPES tt_familymember_c TYPE SORTED TABLE of ts_familymember_c with UNIQUE key pid.

  "late numbering用
  TYPES: BEGIN OF ts_ln_person,
           person_id TYPE ze_person_id,
         END OF ts_ln_person.
  TYPES: BEGIN OF ts_ln_person_mapping,
           pid TYPE  sysuuid_x16,
           final       TYPE ts_ln_person,
         END OF ts_ln_person_mapping.
  TYPES tt_ln_person_mapping TYPE STANDARD TABLE OF ts_ln_person_mapping WITH DEFAULT KEY.

  TYPES: BEGIN OF ts_ln_familymember,
           person_id TYPE ze_person_id,
           member_id TYPE ze_member_id,
         END OF ts_ln_familymember.
  TYPES: BEGIN OF ts_ln_familymember_mapping,
           pid TYPE sysuuid_x16,
           final       TYPE ts_ln_familymember,
         END OF ts_ln_familymember_mapping.
  TYPES tt_ln_familymember_mapping TYPE STANDARD TABLE OF ts_ln_familymember_mapping  WITH DEFAULT KEY.
4.2.2 Adapterクラスの変更

ローカルクラス
createメソッドの定義でインポートパラメータの型を変更します。ロジックの変更はありません。

    METHODS buffer_person_for_create IMPORTING is_person TYPE zif_person_adapter=>ts_person_c.
    METHODS buffer_familymember_for_create IMPORTING is_familymember TYPE zif_person_adapter=>ts_familymember_c.

saveメソッドでは、登録用の型(pid付き)からpidを除く処理を追加します。

  METHOD save.
    "pidなしにする
    data(lt_perton) = get_person_create( ).
    data(lt_familymember) = get_familymember_create(  ).

    INSERT zperson_u FROM TABLE @lt_perton.
    INSERT zfamilymember_u FROM TABLE @lt_familymember.
  ENDMETHOD.

  METHOD get_person_create.
    data ls_person type zperson_u.

    loop at mt_create_buffer_person ASSIGNING FIELD-SYMBOL(<buffer>).
      ls_person = <buffer>-person.
      insert ls_person into table rt_person.
    ENDLOOP.

  ENDMETHOD.

  METHOD get_familymember_create.
    data ls_member type zfamilymember_u.

    loop at mt_create_buffer_familymember ASSIGNING FIELD-SYMBOL(<buffer>).
      ls_member = <buffer>-familymember.
      insert ls_member into table rt_familymember.
    ENDLOOP.

  ENDMETHOD.

グローバルクラス
createメソッドのインポートパラメータの型を変更します。

    METHODS create_person IMPORTING is_person TYPE zif_person_adapter=>ts_person_c.
    METHODS create_familymember IMPORTING is_familymember TYPE zif_person_adapter=>ts_familymember_c.
4.1.3. Behavior Implementationクラスの変更

createメソッドでpidにUUIDを採番して設定し、それをAdapterクラスのcreateメソッドに渡します。また、mappedパラメータに%cidと%pidのマッピングを返します。

  METHOD create.
    DATA person_c TYPE zif_person_adapter=>ts_person_c.

    LOOP AT entities ASSIGNING FIELD-SYMBOL(<person>).
      person_c-person = CORRESPONDING #( <person> MAPPING FROM ENTITY USING CONTROL ).
      person_c-pid = cl_system_uuid=>create_uuid_x16_static( ). "pid採番
      zcl_person_adapter=>get_instance(  )->create_person( person_c ).

      insert value #( %cid = <person>-%cid
                      %pid =  person_c-pid )
                     into table mapped-person.
    ENDLOOP.

  ENDMETHOD.

  METHOD cba_familymember.
    DATA familymember_c TYPE zif_person_adapter=>ts_familymember_c.

    LOOP AT entities_cba ASSIGNING FIELD-SYMBOL(<person>).
      LOOP AT <person>-%target ASSIGNING FIELD-SYMBOL(<member>).
        familymember_c-familymember = CORRESPONDING #( <member> MAPPING FROM ENTITY USING CONTROL ).
        familymember_c-familymember-person_id = <person>-PersonID.
        familymember_c-pid = cl_system_uuid=>create_uuid_x16_static( ). "pid採番
        zcl_person_adapter=>get_instance(  )->create_familymember( familymember_c ).

      insert value #( %cid = <member>-%cid
                      %pid =  familymember_c-pid )
                     into table mapped-familymember.
      ENDLOOP.
    ENDLOOP.
  ENDMETHOD.

4.3 Late Numberingの実装

adjust_numbersメソッドで%pidと採番したキーを返せるようにします。

4.3.1. Adapterクラスの実装

ローカルクラス
adjust_numbersメソッドを以下のように実装します。ここが実際の採番とキーの紐づけを行う部分になります。

メソッド定義

    METHODS adjust_numbers EXPORTING et_person_mapping TYPE zif_person_adapter=>tt_ln_person_mapping
                                     et_member_mapping TYPE zif_person_adapter=>tt_ln_familymember_mapping.

実装

  METHOD adjust_numbers.
    DATA new_member_id TYPE zfamilymember_u-member_id.
    DATA lt_create_bufffer_person TYPE zif_person_adapter=>tt_person_c.
    DATA lt_create_buffer_member TYPE zif_person_adapter=>tt_familymember_c.
    DATA ls_member TYPE zif_person_adapter=>ts_familymember_c.

    IF mt_create_buffer_person IS NOT INITIAL.

      "Get max person id from db
      SELECT MAX( person_id ) FROM zperson_u INTO @DATA(new_id).
      LOOP AT mt_create_buffer_person INTO DATA(ls_person).
        CLEAR new_member_id.
        new_id = new_id + 1.

        "read associated members
        LOOP AT mt_create_buffer_familymember INTO ls_member
          WHERE familymember-person_id = ls_person-person-person_id.
          new_member_id = new_member_id + 1.
          APPEND VALUE #( pid = ls_member-pid
                          final = VALUE #( person_id = new_id
                                           member_id = new_member_id ) )
                          TO et_member_mapping.
          ls_member-familymember-person_id = new_id.
          ls_member-familymember-member_id = new_member_id.
          INSERT ls_member INTO TABLE lt_create_buffer_member.
        ENDLOOP.

        APPEND VALUE #( pid = ls_person-pid
                        final = VALUE #( person_id = new_id ) )
                        TO et_person_mapping.
        ls_person-person-person_id = new_id.
        INSERT ls_person INTO TABLE lt_create_bufffer_person.
      ENDLOOP.

    ELSEIF mt_create_buffer_familymember IS NOT INITIAL.

      LOOP AT mt_create_buffer_familymember INTO ls_member.
        IF new_member_id IS INITIAL.
          SELECT MAX( member_id ) FROM zfamilymember_u
            WHERE person_id = @ls_member-familymember-person_id
            INTO @new_member_id.
        ENDIF.
        new_member_id = new_member_id + 1.
        APPEND VALUE #( pid = ls_member-pid
                        final = VALUE #( person_id = ls_member-familymember-person_id
                                         member_id = new_member_id ) )
                        TO et_member_mapping.
        ls_member-familymember-member_id = new_member_id.
        INSERT ls_member INTO TABLE lt_create_buffer_member.
      ENDLOOP.
    ENDIF.

    mt_create_buffer_person = lt_create_bufffer_person.
    mt_create_buffer_familymember = lt_create_buffer_member.

  ENDMETHOD.

グローバルクラス
Behavior Implementationクラスから呼ばれるadjust_numbersメソッドを以下のように実装します。

メソッド定義

    METHODS adjust_numbers exporting et_person_mapping type zif_person_adapter=>tt_ln_person_mapping
                                     et_member_mapping type zif_person_adapter=>tt_ln_familymember_mapping.

実装

  METHOD adjust_numbers.
    lcl_person_buffer=>get_instance( )->adjust_numbers(
      IMPORTING
        et_person_mapping = et_person_mapping
        et_member_mapping = et_member_mapping
    ).
  ENDMETHOD.
4.3.2. Behavior Implementationクラスの実装

Behavior ImplementationクラスからAdapterクラスのadjust_numbersを呼び、返ってきたpidとキーのセットをmappingに返します。

  METHOD adjust_numbers.
    zcl_person_adapter=>get_instance(  )->adjust_numbers(
      IMPORTING
        et_person_mapping = DATA(lt_person_mapping)
        et_member_mapping = DATA(lt_member_mapping)
    ).

    mapped-person = VALUE #( FOR person IN lt_person_mapping
                             ( %pid = person-pid
                               PersonID = person-final-person_id ) ).

    mapped-familymember = VALUE #( FOR member IN lt_member_mapping
                                    ( %pid = member-pid
                                    PersonID = member-final-person_id
                                    MemberID = member-final-member_id ) ).

  ENDMETHOD.

動作確認(2)

新規データを登録します。最初の段階ではキーは採番されていません。
image.png

"Create"ボタンを押すと、キーが採番されました。
image.png

問題

登録はうまくいったのですが、"Edit"ボタンを押すと以下のエラーが出てしまいます。
image.png
デバッグしたところ、ロックの処理でエラーが起きていることはわかりましたが原因はわかりませんでした。
image.png
区切りをつけるため一旦この記事は公開し、次のUpdate, Delete編で問題を解決させたいと思います。

2022/2/19 更新
今回のBOはドラフトありで作成していました。試しにドラフトなしに変更してみるとEditを押すことができました。(※OData V4はドラフト有効化しないとEditボタンが出てこないので、V2のService Bindingを作成)
ドラフトありの場合、Managed BOだとEditを押したときにドラフトエンティティが作られます。推測ですが、Unmanagedの場合はドラフトが自動的に作成されないため、フレームワークはドラフトがある前提で動作し、実際にはドラフトがないためエラーになっているのではないかと考えています。
SAP Communityに投稿した質問

試しにlockメソッドの中でドラフトを作成しようとしましたが、ここではMODIFY ENTITYSが許可されないということでエラーになってしまいました。

  METHOD lock.
    "create draft entity in case of update request
    MODIFY ENTITIES of zi_person_u in LOCAL MODE
      ENTITY Person
        EXECUTE edit from
        value #( for key in keys ( PersonID = key-PersonID ) )
        REPORTED data(edit_reported)
        FAILED data(edit_failed)
        MAPPED data(edit_mapped).

    reported = CORRESPONDING #( edit_reported ).
    failed = CORRESPONDING #( edit_failed ).
    mapped = CORRESPONDING #( edit_mapped ).

  ENDMETHOD.

今のところどうにもならないため、以降はドラフトなしでUpdate, Deleteの実装をしたいと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?