添加 ABAP 逻辑
学习目标
实施业务对象的行为。
校验
检查语义键值

在 ABAP RESTful 应用程序编程模型中,数据库表的键值通常由客户端字段和 UUID 字段组成,该字段的值由运行时在创建业务对象的新实例时自动分配。此字段组合足以确保系统可以唯一标识表中的每个记录。但是,除了此技术代码外,我们的对象还具有语义代码 - 在本例中为航空公司和航班号的组合,根据业务逻辑,该键值也必须唯一。为确保此字段组合的唯一性,必须以验证形式实施自己的检查。
在 CDS 视图实体的行为定义中声明验证,并在行为实施类中实施这些验证。
应用中的输入检查
除检查语义代码外,还需要执行其他检查。例如,尽管生成的应用允许您创建、读取、更新和删除数据,但它尚不包含任何一致性检查。因此,您可以为不存在的航空公司或出发机场和目的地机场相同的航空公司创建航班转机。

为防止发生这种情况,请在行为定义中定义进一步验证,并在行为实施类中实施这些验证。
创建消息文本
创建验证之前,必须创建要显示的文本。使用消息类执行此操作。消息类是属于特定应用程序范围的最多 1000 条消息的集合。如图所示,每个文本都有一个编号,用于在消息类中唯一标识消息。

要创建新的消息类,请执行以下操作:
选择 文件→新建→其他… ,然后在过滤器字段中输入消息。
双击命中列表中的消息类条目,然后为新消息类输入包、名称和描述。选择下一步。
将消息类分配到传输请求,然后选择完成。
消息还可以包含占位符,在显示消息时会将其替换为具体值。占位符由 与 符号后跟数字表示。每条消息中最多可以使用四个占位符。
定义验证
要定义验证,请将验证声明添加到业务对象的行为定义。在此示例中,应在用户保存数据记录时执行验证,这可以是创建记录时或随后更改。

在行为定义中定义验证时,警告会告诉您相应的方法不存在。使用快速修复(组合键 CTRL + 1)将 方法添加到行为实施。行为实施是行为池中的局部类。方法定义包含附加项 FOR VALIDATE ON SAVE,将其标识为验证的实施。它具有导入参数 KEYS。这是一个内部表,其中包含已创建或已更改对象的键值。使用它们读取用户输入的实际数据。
附加项 FOR Connection~CheckSemanticKey 将方法与行为定义中的验证 CheckSemanticKey 相链接。在此,连接是视图实体 Z_R_CONNECTION 的别名。

定义验证时,还必须创建其实施。这是行为池中的方法。最简单的方法是使用快速修复。将光标置于验证名称上,然后按 CTRL + 1。ADT 建议创建方法。双击建议以创建方法。
验证流程
注意
本节中的某些代码示例在循环内使用 SELECT 语句。这样做是为了简化示例。请注意,循环中的 SELECT 可能会导致性能问题,应避免这种情况。

系统触发验证时,会调用相应的实施。导入参数 KEYS 包含已更改数据记录的键值。使用键值使用实体操作语言 (EML) 读取所需记录的字段。EML 是 ABAP 中允许您处理业务对象的特殊语句集。
读取数据后,可以执行所需的检查。如果检查失败,则需要发出相应的错误消息,重要的是告知框架不要将更改写入数据库。

验证中的第一个任务是读取用户输入。使用实体操作语言 (EML) 语句 READ ENTITIES 执行此操作。使用导入参数键值将相应数据记录的键值传递到验证。
验证语义键所需的字段为航空公司的 CarrierID 和航班号的 ConnectionID。
代码片段使用 CORRESPONDING 关键字和结果集的内联声明。在下面,您会看到使用显式定义的变量的等效代码,这样更易于理解所使用的类型。
DATA read_keys TYPE TABLE FOR READ IMPORT zs4d400_r_connection.
DATA connections TYPE TABLE FOR READ RESULT zs4d400_r_connection.
read_keys = CORRESPONDING #( keys ).
READ ENTITIES OF zs4d400_r_connection IN LOCAL MODE
ENTITY Connection
FIELDS ( CarrierID ConnectionID )
WITH read_keys
RESULT connections.
读取用户输入后,可以使用 CarrierID 和 ConnectionID 的值来查看此语义键是否已在其他数据集中使用,而不是刚刚处理的数据集。由于键值组合可能位于活动表或草稿表中,因此您需要同时查看两者,而最有效的方法是使用联合。

此查询的结果集应始终为空。如果不是,则更多的记录具有 CarrierID 和 ConnectionID 的相同组合,这意味着用户当前尝试创建的记录是重复的,必须被拒绝。

如果承运商标识和连接标识的组合已存在,则表 check_result 中将有一个条目。在这种情况下,必须发出消息。
第一步是创建消息对象。使用自我参考我并调用方法 new_message() 执行此操作。参数标识、编号和**严重性为必填项。ID 是包含消息的消息类的名称;number 是消息编号。严重性将消息分类为成功、信息、警告或错误消息。行为实施类包含结构化常量 ms,其组件代表不同的严重性级别。在这种情况下,您需要严重性级别 ms-error。
该方法还具有可选导入参数 v1、v2、v3 和 v4。使用这些项将占位符替换为具体值。在此示例中,占位符 &1 替换为航空公司代码,占位符 &2 替换为航班号。
方法调用的结果是对象参考。在下一步中,将对象传递到运行时,以便错误消息返回 OData 服务并显示在应用预览中。

要使运行时显示消息,必须使用报告的结构进行报告。这是所有验证方法的隐式更改参数,是深度结构。它包含具有实体别名的组件。此组件为内部表。
要报告消息,必须执行以下三项操作:
将受影响记录的键值添加到内部表。可以使用字段组
执行此操作。对字段进行分组时,可以寻址组的名称,而不必单独寻址每个字段。 2. 将消息对象附加到表。通过将消息对象的对象参考分配到内部表的
组件来执行此操作。 3. 将消息绑定到受影响的字段。这可确保在 应用中强调 字段。反过来,这有助于用户更好地导航应用。使用内部表的
组件执行此操作。

在此示例中, reported_record 是具有内部表 reported-connection 的行类型的结构。使用结构连接中 %tky 字段组的内容填充 %tky 组件。此连接结构用作内部表的工作区域,其中包含用户输入的数据。接下来,将使用 new_message( ) 方法创建的消息对象指派给 %msg 组件。最后,要将消息绑定到字段 CarrierID 和 ConnectionID,请使用结构 %element。%element 包含实体中每个字段的组件。如果将组件设置为真,则相应的输入字段将在应用中突出显示。使用结构化常量 if_abap_behv=>mk 执行此操作。针对 checked/true 打开组件,针对未检查/false 设置为 off。
此时无法使用全局常量 abap_true 和 abap_false,因为它们的数据类型不兼容。

除发出消息外,还必须告知运行时不要保存错误数据。为此,使用验证方法的失败结构。失败是所有验证方法中都存在的隐式更改参数。
要将记录报告为失败,请将其字段组 %tky 添加到内部表 failed-Connection 的字段组 %tky。

下一个验证会检查用户输入的航空公司是否确实存在。第一步是使用 EML 语句 READ ENTITIES 读取用户输入。此时,您只需要读取字段 CarrierID。

SELECT SINGLE 语句使用 CDS 视图实体 /dmo/i_carrier 读取数据并检查给定航空公司是否存在。如果是,则全局常量 abap_true ('X') 的值将放置在该字段中。如果 SELECT 语句之后的 exists 为初始值,则必须发出一条消息,对其进行报告,并将记录添加到失败的结构,如上一示例中所示。
最终验证检查始发地和目的地机场是否不同。第一步是使用 READ ENTITIES 语句读取用户输入。此时,与 AirportFromID 和 AirportToID 字段相关。


如果出发机场和到达机场相同,则必须发出相应的消息并填写报告和失败的结构。代码提取显示用于创建消息的相关编码。用于填充已报告和失败结构的编码与先前示例中的相同。
如何验证语义键值
演示:观看演示(SAP 官方交互式练习)
确定
根据机场代码确定城市
在示例应用中,转机实体包含出发机场、城市和国家/地区以及到达机场、城市和国家/地区。虽然可以强制用户输入所有这些信息,但在用户体验和数据一致性方面,最好让用户仅输入机场代码,并使应用从数据库中读取相应的城市和国家/地区信息。在 ABAP RESTful 应用程序编程模型中,您可以使用确定执行此类任务。

您将首先实施确定。之后,您将了解如何取消激活将自动填充的字段的输入。
定义确定
在业务对象的行为定义中定义确定。此处的确定称为 getCities,每当保存业务对象且至少一个 AirportFromID 和 AirportToID 字段发生更改时,就会调用该确定。您可以在行为定义中使用快速修复,以在行为实施中创建相应的方法。

确定流程
让我们了解确定流程的每个步骤。

系统触发确定时,会调用相应的实施。导入参数 KEYS 包含已更改数据记录的键值。在确定方法中,使用 EML 读取基于代码的数据,方法与验证中的方式完全相同。但是,在确定中,您还会操作方法中的数据,因此必须使用 EML 语句 UPDATE 更新框架保存的数据。
注意
本节中的某些代码示例在循环内使用 SELECT 语句。这样做是为了简化示例。请注意,循环中的 SELECT 可能会导致性能问题,应避免这种情况。

在确定开始时,使用 EML 读取用户输入。您需要 AirportFromID 和 AirportToID 字段,并使用这些字段填写城市和国家/地区信息。

演示数据模型提供 CDS 视图实体 /dmo/i_airport,可用于读取特定机场所在的城市和国家/地区。该示例使用 INTO 子句的变式,您可以在其中显式指定要填充的 结构的字段。请记住,对数据的更改位于内部表的工作区域中,并且必须使用 MODIFY 语句将其返回到表本身。

READ ENTITIES 语句返回具有派生类型 FOR READ RESULT 的内部表。要更改事务缓冲区中的数据,您需要 MODIFY ENTITIES 语句。使用派生类型为 FOR UPDATE 的内部表将要更改的数据传递给此语句。这两种类型中的数据字段相同,但 FOR UPDATE 表具有名为 %control 的附加结构,其中包含管理信息。
不能将连接表传递给 MODIFY ENTITIES 语句。因此,您需要在执行实际修改之前将数据复制到适当类型的内部表 (connections_upd) 中。

要使用在确定中填充的字段更新数据,请使用 MODIFY ENTITIES 语句。在其中指定应在 FIELDS 子句中更新哪些字段,并使用 WITH 附加在内部表中传递数据。此表必须具有正确的派生数据类型,在本例中为 TYPE TABLE FOR UPDATE zsd4d400_r_connection。
MODIFY ENTITIES 语句可以返回使用 REPORTED 子句接收的消息。然后,通过将内部表的内容复制到确定方法的 已报告 结构,将这些消息传播到自己的业务对象。
如何确定城市和国家/地区
演示:观看演示(SAP 官方交互式练习)
验证航班价格
在本练习中,您将定义并实施航班价格验证。
模板:
无
解决方案:
/LRN/S4D400_R_FLIGHT(行为定义)
/LRN/BP_S4D400_R_FLIGHT(全局类)
前提条件
您已完成之前的练习。您已创建并填充数据库表 Z##FLIGHT(## 为组编号)并生成 OData UI 服务的开发对象。
任务 1: 验证价格
定义并实施验证以检查**价格字段是否具有正值(建议名称:validatePrice**)。如果值为负或等于零,则拒绝更改并从消息类 /LRN/S4D400 报告合适的错误消息。
步骤
在行为定义
ZR_##FLIGHT中,定义新的验证validatePrice。确保始终在标准工序**创建期间执行验证,但前提是所有其他工序的价格**值已更改。
提示
尽可能使用代码完整化输入代码。
打开行为定义
ZR_##FLIGHT。如下调整代码:
create;
update;
delete;
validation validatePrice on save { create; field Price; }
激活行为定义。
按
Ctrl + F3激活行为定义。使用快速修复在行为处理器类中创建验证实施方法。
将光标置于验证名称上,然后选择 Ctrl + 1。
双击条目 Add method for validation....
在
validatePrice方法的开头,声明使用**失败航班的行类型键入的结构化数据对象(建议名称:failed_record)。同样,声明您键入的具有reported-flight行类型的结构化数据对象(建议名称:reported_record**)。
注意
这些结构将用于向失败的航班和报告航班添加行,以防验证发现错误。
添加以下代码:
DATA failed_record LIKE LINE OF failed-flight.
DATA reported_record LIKE LINE OF reported-flight.
使用 READ ENTITIES 语句从事务缓冲区读取用户输入。使用 IN LOCAL MODE 附加并确保仅读取关键字段和**
价格字段。对结果集使用内联声明(建议名称:航班**)。
注意
不必在 FIELDS 添加后列出关键字段。READ ENTITIES 始终读取关键字段。
申报后,添加以下代码,将
##替换为您的组编号:
READ ENTITIES OF ZR_##Flight IN LOCAL MODE
ENTITY Flight
FIELDS ( Price )
WITH CORRESPONDING #( keys )
RESULT DATA(flights).
对刚刚读取的数据实施循环。将内联声明用于工作区(建议名称:
航班)。在 EML 语句后,添加以下代码:
LOOP AT flights INTO DATA(flight).
ENDLOOP.
在循环中,检查**
价格**组件是否大于零。如果不是,则使用当前航班的键值填充结构failed_record,并将其作为新行添加到表failed-flight。同样,使用当前航班的键值填充reported_record结构,并将其作为新行添加到表reported-flight。
提示
在启用草稿的业务对象中,建议您使用组件%tky 分配键值。
在 循环中,添加以下代码:
IF flight-price <= 0.
failed_record-%tky = flight-%tky.
APPEND failed_record TO failed-flight.
reported_record-%tky = flight-%tky.
APPEND reported_record TO reported-flight.
ENDIF.
在 APPEND 语句之前,通过引用消息对象填充
reported_record结构的%msg组件。要创建消息对象,请使用以下输入调用new_message方法: | 参数名称 | 值 | | --- | --- | |id| '/LRN/S4D400' | |number| '101' | |严重性| ms-error |如下调整代码:
LOOP AT flights INTO DATA(flight).
IF flight-price <= 0.
failed_record-%tky = flight-%tky.
APPEND failed_record TO failed-flight.
reported_record-%tky = flight-%tky.
reported_record-%msg = new_message(
id = '/LRN/S4D400'
number = '101'
severity = ms-error ).
APPEND reported_record TO reported-flight.
ENDIF.
ENDLOOP.
激活此类。
按
Ctrl + F3激活类。
任务 2: 测试和调试
在验证实施中设置断点。重新启动 OData UI 服务的预览并更改现有航班的价格。输入有效和无效条目,然后调试验证。
步骤
在
validatePrice方法的 READ ENTITIES 语句中设置断点。在
validatePrice方法的实施中,找到 READ ENTITIES 语句并双击行编号左侧的区域以设置断点。重新启动 OData UI 服务的预览。
关闭浏览器窗口或包含预览的浏览器标签。
打开
ZUI_##FLIGHT_O4服务绑定。在右侧的实体集和关联列表中,首先选择航班条目,然后选择预览....在应用中,显示航班列表,打开其中一个航班的详细信息并切换到更改模式。
按照先前练习中的操作继续操作。
对价格进行一些更改,然后选择保存。在调试器中分析验证。
按照先前练习中的操作继续操作。