はじめに
この記事では、前回の記事に続き、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よりも前に行われており、下図のやり取りが発生するのは「保存」ボタンを押した後でした。
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でエラーになった場合にバッファのデータを初期化する。また、データベースの更新ロックを解除する |
Adapterクラスの作成
Adapterクラスを作成し、Behavior Implementationクラスと役割を分担します。この考え方はSAP Pressの書籍ABAP RESTful Programming Modelを参考にしました。以下の図には、簡単にするためにLOCK, FINALIZE, CHECK_BEFORE_SAVEなどは入れていません。
以下が役割分担です。
Behavior Implementationクラス
- エンティティの項目をDBが受け取る形式にマッピングする
- Adapterクラスを呼び出してバッファへのデータ格納やDBへの保存を行う
Adapterクラス
- チェックや値の代入などのビジネスロジックの適用
- CREATE, UPDATE, DELETE処理ではハンドラクラスから受け取った値をバッファに格納する
- SAVEでバッファからDBにデータを保存する
- INITIALIZEでバッファを初期化する
上記ではSAVE処理が直接DBを更新していますが、BAPIなどを呼んで更新するケースもあると思います。BAPIを使うときに注意したいのは、SAVEでバリデーションエラーが発生しても画面にエラーは返せないので、CHECK_BEFORE_SAVEなどで事前にチェックをかけておく必要があるということです。
実装ステップ
今回はPersonとFamily Memberの登録、採番ができるところまでをゴールとします。
- Behavior Definitionにクラスをひもづける
- Adapterクラスの作成
- Behavior Implementationクラスの実装
- 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クラスの実装
create
とsave
, 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を聞かれるので手で入力します。
必要な項目に入力し、Family Membersも登録します。
Member IDを手入力します。
入力が終わり"Create"ボタンを押すと、データが保存されました。
4. Late Numberingの実装
Person IDおよびMember IDをLate Numberingで採番するようにします。このためには、Saverクラスのadjust_numbers
を実装します。
以前、以下の記事でManaged BOでのLate Numberingについて扱いました。
【RAP】Late Numbering
Managedの場合、adjust_numbers
でやることは仮のキーと採番したキーのマッピングを返すことだけでした。Unmanagedの場合、これに加えて登録用のバッファに持っているキーを採番したキーで更新する必要があります。
処理概要
まず、createのタイミングで仮キーとなる%pidを採番し、%cidとのマッピングをmappedというパラメータに返します。次に、adjust_numbersで%pidと実際に採番されたキーのマッピングを返します。これにより、RAPのフレームワークがLate Numberingで採番したキーを登録されたエンティティと紐づけてくれます。
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)
新規データを登録します。最初の段階ではキーは採番されていません。
問題
登録はうまくいったのですが、"Edit"ボタンを押すと以下のエラーが出てしまいます。
デバッグしたところ、ロックの処理でエラーが起きていることはわかりましたが原因はわかりませんでした。
区切りをつけるため一旦この記事は公開し、次の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の実装をしたいと思います。