原文連結:Adding ABAP logic
在ABAP RESTful API中,資料表的鍵通常由client欄位與UUID欄位組成,其值在建立新的實體時會由系統自動指派。這類型的欄位可以確保資料被唯一識別。
然而,除了前述的technical key,在物件中還有另一個語意化的鍵,以前一章的例子而言是航空公司與航班的組合鍵,其組合也必須具有唯一性。
為了確保資料的唯一性,我們需要設置檢查主鍵的Validation。可以在CDS視圖的行為定義(behavior definition)中宣告Validation,並於行為實例(behavior implementation)的class中實例化。
雖然應用可以對資料進行增刪改查,但目前還未包含一致性的檢查,可能導致創建不存在的航空公司、或者出發和抵達機場一樣的錯誤情境。
因此,在設計時也需要考量諸多的Validation條件。
在我們建立Validation之前,需要先建立想提示的文字,這時可以使用message class。message class是一個可以存入1000則提示訊息的類別,且每則訊息都有一個獨立編號,如圖所示:
在File → New → Other
中可以搜尋message
,選擇message class
。
2.
輸入編號及想顯示的訊息
訊息中還可以使用佔位符&<數字>
,在顯示時會轉換成對應的值。每則訊息中最多能使用四個佔位符。
在BO的行為定義中,我們可以定義前述的那些Validation。Validation應在資料被保存時就進行驗證,例如新增或修改時。
"在行為定義中"
validation CheckSemanticKey
on save { create; update; }
當在行為定義中寫Validation的時候,系統會提示該方法不存在,此時可以對Validation按下ctrl+1並雙擊,在行為實體中添加方法。
"在行為實體中"
CLASS 1cl_handler DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS CheckSemanticKey
FOR VALIDATE ON SAVE
IMPORTING keys FOR
Connection~CheckSemantickey.
ENDCLASS.
行為實體將會是behavior pool中的local class。該方法中的FOR VALIDATE ON SAVE
語句可視為Validation的實例化。
這裡有一個導入參數'keys',這是一個存放了被新增或修改的鍵的internal table,可以用來讀取使用者輸入的實際資料。
FOR Connection~CheckSemanticKey
則用來把這個方法與行為定義的CheckSemanticKey
Validation進行連結,Connection
是CDS視圖的別名。
當Validation被觸發,相應的實例也會被呼叫,可以透過參數'keys'結合EML讀取所需的資料欄位。
當我們讀取到資料,此時可以執行開發者設定好的Validation。一旦驗證失敗,會在此時顯示錯誤訊息,並通知系統不要寫入這個變更到資料庫。
接著會用存取航班的三個Validation範例,逐一介紹上圖中驗證流程的寫法,主要在Validation 1中會有流程詳述,其餘會簡單概述。
"定義讀入表格"
DATA read_keys TYPE TABLE FOR READ IMPORT zs4d400_r_connection.
"定義輸出結果表格"
DATA connections TYPE TABLE FOR READ RESULT zs4d400_r_connection.
"自動對應keys參數"
read_keys = CORRESPONDING #( keys ).
"指定要讀取的行為定義"
READ ENTITIES OF zs4d400_r_connection IN LOCAL MODE
ENTITY Connection"讀取的實體"
FIELDS (UUID CarrierID ConnectionID )"欄位列表"
WITH read_keys"鍵列表"
RESULT connections."存放結果到result internal table"
這裡使用到了前面章節有提及的EML,新增的資料會透過參數'keys'傳給Validation。
在需要被驗證的資料裡,其語意化鍵欄位有代表航線的CarrierID
以及代表航班號碼的ConnectionID
,另外也需要UUID欄位。
當成功讀入使用者輸入的資料,可以使用可以使用 CarrierID
和 ConnectionID
的值來檢查是否已被處理。由於組合鍵可能會在草稿或已啟用的表格中,可以使用union
來同時查看這兩個表:
LOOP AT connections INTO DATA(connection).
SELECT FROM zs4d400aconn
FIELDS uuid
WHERE carrier_id = @connection-Carrier ID
AND connection_id = @connection-ConnectionID
AND uuid <> @connection-uuid
UNION
SELECT FROM zsd400dconn
FIELDS uuid
WHERE carrierid = @connection-CarrierID
AND connectionid = @connection-Connection ID
AND uuid <> @connection-uuid
INTO TABLE @DATA(check_result).
ENDLOOP.
這個查詢結果應為空,如果不為空,代表存在相同的CarrierID
與ConnectionID
的組合,也就是使用者目前建立的資料(鍵)是重複的,必須拒絕。
當前述的鍵重複組合已經存在,搜尋將會被存入表格check_result
當中,這時我們可以透過確認該表格是否有內容,回傳錯誤提示。
"檢查表格是否有回傳結果"
IF check_result IS NOT INITIAL.
DATA(message) = me->new_message(
id = 'ZS4D400' "message class名稱"
number = '001' "編號"
severity = ms-error "嚴重性"
v1 = connection-CarrierID "佔位符v1"
v2 = connection-ConnectionID )."佔位符v2"
ENDIF.
首先需要先在message class中建立錯誤訊息,可以使用me
自我引用並呼叫方法new_message( )
,參數ID、訊息編號(number)和嚴重性(severity)都是必填項。
參數ID是包含該錯誤訊息的message class名稱;嚴重性則可分為成功、訊息、警告與錯誤,行為實例宣告嚴重性前會接一個常數ms-
,例如本例中的ms-error
。
另外,錯誤訊息還可包含最多四個導入參數v1~v4(可選),當顯示錯誤訊息就會出現對應值,上例的對應值是兩個組合鍵的內容。
呼叫上述方法的結果會是引用一個物件,下一步將傳遞該物件,讓錯誤訊息可以回傳給OData,並在應用程式中顯示。
設置完錯誤訊息,此時使用reported
structure來進行回傳,這是一個隱性的變更參數(changing parameter)及structure,擁有一個包含BO別名的欄位,這個欄位則是一個internal table。
為了回傳訊息,需要以下三步驟:
將受Validation影響的鍵添加到internal table中,這裡可以使用@tky
對欄位分組,直接選取欄位組的名稱而無須指定每一欄。
將錯誤訊息的物件加到表格中,這裡可以把該物件指派給internal table中的%msg
欄位
綁定錯誤訊息與受影響欄位,以確保在應用程式中被強調,並且這也能作為導覽使用者的資訊,使用internal table中的%element
欄位綁定@tky
與@msg
。
"建立回報用structure"
DATA reported_record LIKE LINE OF reported-connection.
reported_record-%tky = line-%tky. "存入受影響的鍵"
reported_record-%msg = msg. "存入錯誤訊息"
reported_record-%element-CarrierID = if_abap_behv=>mk-on. ""
"3. 存入回報用internal table"
APPEND reported_record TO reported-connection.
如上例,reported_record
structure的資料型別來自internal tablereported-connection
,可以使用structure中的%tky
填入被變更的鍵(1),這欄將作為workarea,並從保存了使用者輸入值的internal table中提取資料。
接著,將剛剛用new_message( )
建立的錯誤訊息存入欄位%msg
(2)。
最後,為了將錯誤訊息與欄位CarrierID
相互綁定(3),使用structure%element
,其包含了視圖中的所有欄位。
如果將欄位設置為true
,相關聯的輸入欄位會在在應用程式中高亮顯示。可以使用structure中的結構常數if_abap_behv=>mk-on
及if_abap_behv=>mk-off
來達成,用on
與off
來代表檢查/true
與不檢查/false
。
注意,這裡不能使用全域常數abap_true
和 abap_false
,因為它們的型別是不相容的。
除了傳遞錯誤訊息,也要向系統通知拒絕存入不正確的資料。這裡使用failed
structure來進行Validation,failed
是一個存在於所有Validation方法中的隱式變更參數,當驗證失敗時就可回報該參數以避免資料存入。
若要將報告回報為failed
,要將該欄位群組%tky
加到failed-Connection
這個internal table的%tky
欄位群組中。
下一個Validation要檢查的是使用者輸入的航空公司是否真實存在,如下例,首先先用EMLREAD ENTITIES
讀入使用者的輸入資料,在這個例子僅需讀取欄位CarrierID
。
READ ENTITIES OF zs4d400_r_connection IN LOCAL MODE
ENTITY Connection
FIELDS (CarrierId )
WITH CORRESPONDING #( keys)
RESULT DATA(connections).
接著,用SELECT SINGLE
語句讀取CDS視圖/dmo/i_carrier
,並檢查給定的航班是否存在。
如果存在,全域常數abap_true
('X')會被置於目標航班的欄位中以供偵測欄位存在。如果搜尋結果RESULT仍維持初始值,代表沒搜到相關欄位,需要用下面的if條件式呼叫錯誤題示並記錄在failed
structure中,如同上一個範例。
LOOP AT Connections INTO DATA(line).
SELECT SINGLE FROM /dmo/i_carrier FIELDS @abap_true WHERE AirlineID = @line-CarrierID INTO @DATA(exists).
"如果abap_true為初始值"
IF exists <> abap_true
"此區放置錯誤訊息傳遞及回報"
...
ENDIF.
ENDLOOP.
由於出發機場與抵達機場不可相同,最後一個檢查會來檢查這兩個欄位的輸入值是否重複。第一步一樣先用READ ENTITIES
讀入使用者輸入的資料,這次要讀AirportFromID
與AirportToID
欄位的值。
READ ENTITIES OF zs4d400_r_connection IN LOCAL MODE
ENTITY Connection
FIELDS (AirportFromID AirportToID )
WITH CORRESPONDING #( keys )
RESULT DATA (connections).
LOOP AT connections INTO DATA(connection).
"如果出發地與抵達地相同"
IF connection-airportfromID = connection-airporttoID.
"建立錯誤訊息"
DATA(message) = me->new_message(
id = 'ZS4D400'
number = '003'
severity = ms-error ).
"此區放置錯誤訊息傳遞及回報"
...
ENDIF.
ENDLOOP.
當出發及抵達的機場相同,則顯示相應錯誤訊息(上例)並填入保存錯誤的structures(如前例)。
明天來添加Determination的程式邏輯!