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

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

シリーズの先頭はこちら☞ [【RAP】Unmanaged BOの実装 (1)](https://qiita.com/tami/items/7d19052b1866f7aaf69f)

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

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

**グローバルクラス**

```abap
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/441871b4-a617-5275-dbb6-7145b30bbf68.png)


```abap
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](https://help.sap.com/viewer/923180ddb98240829d935862025004d6/Cloud/en-US/ab7459048c7e4ecda98d0b6f51b01e7b.html?q=clearnup_finalize)

## Adapterクラスの作成
Adapterクラスを作成し、Behavior Implementationクラスと役割を分担します。この考え方はSAP Pressの書籍[ABAP RESTful Programming Model](https://www.sap-press.com/abap-restful-programming-model_4988/)を参考にしました。以下の図には、簡単にするためにLOCK, FINALIZE, CHECK_BEFORE_SAVEなどは入れていません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/f1ab0e49-a0d8-ac34-aeb9-72517ff5c2ca.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にクラスをひもづける
1. Adapterクラスの作成
1. Behavior Implementationクラスの実装
1. Late Numberingの実装

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

```abap
unmanaged implementation in class zbp_i_person_u unique;
```

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

```abap
define behavior for ZI_FAMILYMEMBER_U alias FamilyMember
implementation in class ZBP_I_FamilyMember_u unique
```

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

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

```abap
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. インターフェースを実装してクラスを作成
上記で作成したインターフェースを実装するクラスを登録します。

```abap
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`：バッファを初期化する

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

```abap
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から直接呼ばれる処理）を実装します。処理はローカルクラスに任せるので、対応するローカルクラスを呼ぶだけです。

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

```abap
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設定をコメントアウトします。


```abap
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/64eb1a23-caa9-b581-b563-5cdb4b96d37b.png)
必要な項目に入力し、Family Membersも登録します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/d4f85a72-6bf3-694b-44e4-13aff54e1cdf.png)
Member IDを手入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/f6a376fc-eef9-b6cf-1b8f-cbeb00b4cde3.png)
入力が終わり"Create"ボタンを押すと、データが保存されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/03984346-ebbc-8522-1556-9cad6ccd5115.png)

### 4. Late Numberingの実装
Person IDおよびMember IDをLate Numberingで採番するようにします。このためには、Saverクラスの`adjust_numbers`を実装します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/defc199b-6a7f-d83e-c734-7183355135d4.png)
以前、以下の記事でManaged BOでのLate Numberingについて扱いました。
[【RAP】Late Numbering](https://qiita.com/tami/items/96f20feb5c1f74b22803)
Managedの場合、`adjust_numbers`でやることは仮のキーと採番したキーのマッピングを返すことだけでした。Unmanagedの場合、これに加えて登録用のバッファに持っているキーを採番したキーで更新する必要があります。

#### 処理概要
まず、createのタイミングで仮キーとなる%pidを採番し、%cidとのマッピングをmappedというパラメータに返します。次に、adjust_numbersで%pidと実際に採番されたキーのマッピングを返します。これにより、RAPのフレームワークがLate Numberingで採番したキーを登録されたエンティティと紐づけてくれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/8ea24e77-0fa3-33c6-0c09-4cabd4659c03.png)

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

```abap
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と実際のキーを紐づける型も追加します。

```abap
  "登録用
  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`メソッドの定義でインポートパラメータの型を変更します。ロジックの変更はありません。

```abap
    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を除く処理を追加します。

```abap
  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`メソッドのインポートパラメータの型を変更します。

```abap
    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のマッピングを返します。

```abap
  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`メソッドを以下のように実装します。ここが実際の採番とキーの紐づけを行う部分になります。

メソッド定義

```abap
    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.
```

実装

```abap
  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`メソッドを以下のように実装します。

メソッド定義

```abap
    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.
```

実装

```abap
  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に返します。

```abap
  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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/9e231a19-0af1-d68b-603d-9931edddec56.png)

"Create"ボタンを押すと、キーが採番されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/c38dfc5f-6390-e9a2-b2bf-49e449a12468.png)

## 問題
登録はうまくいったのですが、"Edit"ボタンを押すと以下のエラーが出てしまいます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/a28ab9f4-24f8-7e35-14c1-372201a02964.png)
デバッグしたところ、ロックの処理でエラーが起きていることはわかりましたが原因はわかりませんでした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/907eea83-0f2f-41eb-79cb-26952493df2c.png)
区切りをつけるため一旦この記事は公開し、次のUpdate, Delete編で問題を解決させたいと思います。

**2022/2/19 更新**
今回のBOはドラフトありで作成していました。試しにドラフトなしに変更してみるとEditを押すことができました。（※OData V4はドラフト有効化しないとEditボタンが出てこないので、V2のService Bindingを作成）
ドラフトありの場合、Managed BOだとEditを押したときにドラフトエンティティが作られます。推測ですが、Unmanagedの場合はドラフトが自動的に作成されないため、フレームワークはドラフトがある前提で動作し、実際にはドラフトがないためエラーになっているのではないかと考えています。
[SAP Communityに投稿した質問](https://answers.sap.com/questions/13587072/unmanaged-rap-bo-with-draft-error-when-pressing-ed.html)

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

```abap
  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の実装をしたいと思います。