ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SAP XCO批量生成CDS表函数与AMDP类的完整实践

SAP XCO批量生成CDS表函数与AMDP类的完整实践 先说个我自己的经历。去年帮客户做物料可用性分析需求方一口气提了二十多张报表每张报表的数据源都要经过复杂计算再喂给前端。放到 S/4 体系里最稳的组合就是 CDS View 的表函数Table Function负责把接口暴露给 OData/Fiori配套的 AMDP 类负责在 HANA 上跑 SQLScript 做复杂加工。如果你手动做先要去 SE11 想清楚接口字段再去 SE80 建 CDS 表函数的 DDL然后建 AMDP 类、想类名方法名参数名写完 SQLScript 再回来测。单张表函数还好三五张也能忍到了两位数大部分时间都耗在重复劳动上而且对象长得高度相似复制粘贴最容易手滑。我后来把整套流程改成用 SAP XCO 的模型化 API 批量生成一条程序跑完几十个表函数和配套 AMDP 类全部落地。这篇文章就把这套方法从原理到代码完整摊开适合正在做报表开发、RAP 开发或者被重复建模折磨的 SAP 顾问参考。1. 为什么需要“一次性生成”XCO 解决了什么问题1.1 手工创建表函数 AMDP 类的日常痛点先说个典型场景。S/4 环境里做报表业务特别喜欢“要一个能从多个表实时算出来的数据集”比如把销售订单、采购凭证、生产工单串起来算可用量。这种需求在 ABAP 侧写报表老的思路是 RFC 函数或者 Web Service现在的主流是 CDS 表函数 AMDP。CDS 表函数把接口形态定下来AMDP 类在数据库层做复杂运算。问题来了每做一张表函数你至少要建两个对象。表函数要写 DDL类要写定义和实现类里要标 AMDP 接口、写方法签名、写 SQLScript表函数里要写参数和返回字段。单张还好一旦到了几十张绝大多数时间都浪费在“改名字”和“找参数”上。复制粘贴能提速但也会把错误一起复制走最终你又在排查各种莫名奇妙的激活报错。真正让人崩溃的不是技术难而是重复劳动没有价值。1.2 XCO 是什么不是代码生成器是 ABAP 开发对象的建模框架XCO 全称 Extensible Composition Objects可以理解成一套官方提供的、用 ABAP 代码来创建、修改、读取 ABAP 开发对象的 API 框架。注意它不是普通的“代码生成器”代码生成器是拼字符串XCO 是把 CDS 视图、表函数、类、接口、数据库表这些开发对象抽象成可以编程操作的对象模型。你写 XCO 代码的时候是在描述一个开发对象“应该长什么样”。给 CDS 表函数加字段就调用字段的 builder 设置数据元素给类加方法就调用方法的 builder 声明参数和实现。XCO 内部再把模型定义翻译成标准的 DDL 语法和 ABAP 源码落到 ADT 对象目录里。这套机制的好处是模型结构统一校验过细节不容易错而且它本身就是一段 ABAP 程序天然支持循环、条件判断和批量场景。需要先说一个前提XCO 库不是所有 SAP 系统都有。S/4HANA 2020 及更高版本、SAP BTP ABAP Environment业界常说的 Steampunk都内置了 XCO。如果你的系统比较老这类代码只能做参考落地前要先评估升级条件。我在项目里见过最尴尬的情况是代码写完了一执行才发现XCO_CP_CORE这个类根本不存在所以环境检查一定要放在第一步。1.3 表函数和 AMDP 为什么总是成对出现CDS 表函数在语法上有一个特别的地方它不像普通 CDS View 那样直接在 DDL 里写 select而是声明“我要做成什么样”然后指着某个 AMDP 类的方法说“你去实现”。典型语法是implemented by method zcl_amdp_exampleget_data。所以表函数本身只是外壳真正干活的是 AMDP 方法。AMDP 方法跑在 HANA 上用 SQLScript 写复杂数据库逻辑可以调用 HANA 特性这在普通 ABAP 里做不到。表函数则把计算结果以 CDS 模型暴露出去让 OData、Fiori、其他 CDS 视图都能消费。两者是配套的所以批量生成的时候不能只生成表函数不生成类也不能只生成类不生成表函数。做一次循环把类、方法、表函数全部建出来才是完整的一次性生成。2. 核心细节解析CDS 表函数与 AMDP 类的原理和模型2.1 一个最小表函数的完整结构先把要生成的目标说清楚。一个 CDS 表函数在 DDL 里长这样我用物料文本作为演示EndUserText.label: Material Text by Language define table function ZTF_MATERIAL_TEXT with parameters iv_langu : sy-langu returns { matnr : matnr, maktx : maktx } implemented by method zcl_amdp_material_textget_material_text;三部分结构with parameters声明入参returns声明返回字段集合implemented by method指向实现类和方法。字段类型一般用数据元素参数可以是数据元素或内置类型。普通 CDS View 直接写 select表函数不写 select因为数据来源完全由 AMDP 决定。用 XCO 生成的目标就是这个结构。XCO 的 CDS 入口在xco_cp_cds里面有view、table_function、abstract_entity之类的分支。拿表函数来说先用xco_cp_cdstable_function-for( ZTF_MATERIAL_TEXT )拿到一个 builder然后往这张表函数上添加注解、参数、字段、实现类最后调用创建方法。XCO 会帮你渲染成上面那段 DDL并按照 ABAP 存储库的规则落地。2.2 AMDP 类需要满足的四个硬性条件AMDP 类看起来是普通 ABAP 类但要让表函数认它四个条件缺一不可。第一类要实现IF_AMDP_MARKER_HDB接口。这个接口本身没有方法纯粹是给系统做标记表示“这个类里有 AMDP 实现去 HANA 上编译”。少写了这个接口表函数激活时会直接报“类不是 AMDP 类”。第二方法声明里要有FOR TABLE FUNCTION 表函数名的标注告诉编译器这个方法是用来回填哪张表函数结果的。导出参数的类型就是表函数名本身这点和普通方法很不一样。普通方法导出参数可以随便定义一个表类型AMDP 表函数方法则必须绑定表函数名。第三方法实现里的第一行必须是BY DATABASE FUNCTION FOR HDB LANGUAGE SQLSCRIPT。这是实现的入口声明表示这个方法是数据库函数而不是普通 ABAP 方法。参数传递也受限制AMDP 方法一般只支持表、结构、简单类型不支持传递内部表这种运行时集合。第四方法体内部写的是 SQLScript不是 Open SQL。比如你可以写SELECT ... FROM makt WHERE spras iv_langu再把结果赋给导出参数但不能用SELECT ... INTO TABLE也不能直接调用另一个 ABAP 方法。对 ABAP 开发来说这是思维上最大的转折点。这四个条件都满足AMDP 类和 CDS 表函数才算闭环。2.3 用 XCO 建模时的字段、参数、注解设计在 XCO 里建模表函数要按 XCO 的思维来做而不是按 DDL 字符串的思维。区别在哪里DDL 是一次性渲染出文本XCO 则是“逐步加部件”。你在 XCO 里做的事情和你在 ADT 里一条条添加注解、参数、字段是一样的只是全用代码表达。我常用的顺序是固定的先拿对象 builder再加注解比如EndUserText.label再加输入参数再加返回字段最后指定实现类和实现方法。注解这步最容易忽略但千万别省。很多报表上线后要留给别人维护没有 label 的表函数在 Fiori 里显示不出可读名字也影响 HANA 信息模型的可视化。参数和字段的类型设置我一般用set_type_data_element因为项目里大家约定字段用数据元素能带上文本、域、转换例程等附加信息。如果你需要内置类型比如纯字符串可以用内置类型 API但实际项目里建议优先数据元素后续维护字段语义更清楚。到这一步你已经把“手写 DDL”变成了“填参数模型”批量生成的底座就有了。3. 实操过程用 XCO 批量生成表函数和 AMDP 类3.1 环境检查你的系统配不配跑 XCO开跑之前先做三件事检查 XCO 库、检查包、检查传输请求。XCO 库怎么查最直接的办法是写个程序动态检查类XCO_CP_CORE是否存在。用cl_abap_typedescrdescribe_by_name包在 TRY 里如果抛异常就说明系统没有 XCO。有的话再看xco_cp_cds这个对象里有没有table_function属性。从经验上说S/4HANA 2020 之后的版本都靠谱之前的版本需要打补丁具体以系统里XCO_CP_CORE的可访问属性为准。包和传输请求也要提前想好。XCO 生成对象会往当前传输请求里写你要在代码里把请求号传进去。没有可用请求的情况下我一般先建一个开发包再把对象放进一个新的传输请求里。老项目里如果传输流程卡得严建议一个生成批次对应一个请求方便后面按请求释放和回溯。3.2 先生成 AMDP 类骨架代码与步骤我在生产里的顺序是先建类再建表函数最后统一激活。为什么要先建类不是编译顺序需要而是让后面建表函数指明实现类的时候类对象已经存在于对象目录中。类可以先以“半成品”的形态存在表函数激活时检查到实现类存在就不会倒在第一步。下面这段是 XCO 生成 AMDP 类骨架的核心代码我简化了错误处理方便看主流程DATA(lv_class_name) ZCL_AMDP_MATERIAL_TEXT. DATA(lv_method_name) GET_MATERIAL_TEXT. DATA(lv_tf_name) ZTF_MATERIAL_TEXT. DATA(lo_class) xco_cp_abap_objectsclass-for( lv_class_name ). lo_class-set_public( ). lo_class-set_final( ). AMDP 标记接口 lo_class-add_interface( IF_AMDP_MARKER_HDB ). 方法定义 DATA(lo_method) lo_class-add_method( lv_method_name ). lo_method-set_class_method( ). 生成 CLASS-METHODS DATA(lo_imp) lo_method-add_importing_parameter( IV_LANGU ). lo_imp-set_type_data_element( SYLANGU ). DATA(lo_exp) lo_method-add_exporting_parameter( ET_DATA ). lo_exp-set_type( lv_tf_name ). 表函数名作为返回表类型 不同 XCO 版本对 AMDP 声明的 API 路径略有差异 可以把 FOR TABLE FUNCTION 标注放在方法声明里 也可以在方法实现体的源码里声明。 我项目里更稳妥的做法是把方法体整体用模板源码补齐。 lo_class-create( ).看到这里你可能会问FOR TABLE FUNCTION到底放哪了老实说XCO 不同版本对 AMDP 类创建的 API 支持度不完全一样有的版本方法 builder 里有专门设置 AMDP 表函数的入口有的版本没有。我为了不被版本卡住采用组合方案方法签名用 XCO 的模型化 API 生成整个方法体包括BY DATABASE FUNCTION FOR HDB那段用模板源码补齐最后通过 XCO 的类实现体 API 写进去方法体长这样DATA(lv_impl_source) |METHOD get_material_text BY DATABASE FUNCTION FOR HDB\n| | LANGUAGE SQLSCRIPT\n| | OPTIONS READ-ONLY\n| | USING makt.\n| | et_data SELECT matnr, maktx FROM makt WHERE spras iv_langu;\n| |ENDMETHOD.\n|.之所以建议把实现体做成模板是因为 AMDP 方法体高度模式化头三行声明、中间的 SQLScript、结尾 ENDMETHOD。批量生成时模板里只需要动态替换表名、字段名、参数名比在 XCO 里一点点拼装实现块更可控后期加逻辑也容易。3.3 再生成 CDS 表函数代码与步骤类骨架就位后开始生成表函数。这块 XCO 的支持相对成熟代码也更直观DATA(lo_tf) xco_cp_cdstable_function-for( lv_tf_name ). 注解 lo_tf-add_annotation( EndUserText.label )-set_value( Material Text by Language ). 输入参数 DATA(lo_param) lo_tf-add_parameter( IV_LANGU ). lo_param-set_type_data_element( SYLANGU ). 返回字段 DATA(lo_field_matnr) lo_tf-add_field( MATNR ). lo_field_matnr-set_type_data_element( MATNR ). DATA(lo_field_maktx) lo_tf-add_field( MAKTX ). lo_field_maktx-set_type_data_element( MAKTX ). 实现类与方法 lo_tf-set_implemented_by( lv_class_name ). lo_tf-set_method( lv_method_name ). lo_tf-create( ).这段代码跑下来XCO 会在后台渲染成前面那段 DDL 并落进对象目录。需要注意两点第一表函数名和 AMDP 方法导出参数类型绑定所以ET_DATA的类型直接引用ZTF_MATERIAL_TEXT这个还没完全激活的名字属于设计上的正常用法第二set_implemented_by和set_method这两个方法在不同版本可能合并成一个implemented_by( class, method )如果编译报方法不存在去类里用 F2 看当前版本 builder 提供了哪个入口。这里顺便说下命名规范。我生成的类名、表函数名、方法名全部走配置表生成循环直接读配置。字段名和参数名也尽量在配置表里定义而不是散落在代码里。这样后期加一张表函数只需要往配置表插一行记录生成器不用改任何代码。3.4 统一激活的完整流程与校验对象都建出来了不等于能用。ABAP 存储库最终要看激活版本。我第一次写 XCO 的时候create 之后兴冲冲去 SE80 看结果发现对象是灰的甚至找不到以为生成失败了。其实 XCO 是把对象写进了当前请求激活状态取决于对象间的依赖顺序。标准激活顺序建议这样排先激活 AMDP 类。再激活 CDS 表函数。如果类激活时报“类型不存在”先别慌——它引用的表函数还没激活。这时候回去激活表函数如果表函数又报“实现类不存在”那说明类确实还没能激活成功。打破死锁的办法是确认类已经作为对象存在哪怕激活失败先去激活表函数等 DDIC 类型出现再回头重新激活类。整个过程系统允许先有未激活版本对象目录里能看到。我在代码里习惯用一个循环去“激活”对象。XCO 的对象 builder 一般提供了activate相关入口具体方法名也随版本略有差异。如果没有可以用 SE80 的批量激活选中整个包勾选“同时激活依赖对象”系统会自动处理顺序比在代码里硬想一个完美顺序更省心。激活完成后我至少做三层校验。第一层SE80/ADT 里看对象是不是绿色激活状态。第二层用SELECT * FROM ZTF_MATERIAL_TEXT之类的 Open SQL 直接查一下表函数由于它是实时计算函数能查出来就说明 CDSAMDP 通路是通的。第三层注册一个 OData 服务或者简单在 Fiori 里拉一次数据验证前端消费路径。三层都过了这张表函数才算真正可用。4. 常见问题与排查技巧实录4.1 激活时报“实现类不存在”顺序和类名检查这个报错大多是两个原因。第一生成顺序反了类对象还没在对象目录里就建了表函数。第二类名和方法名写错了CDS 解析对不上。排查时不要光看报错文本先在 SE80 里确认那个类确实存在再双击表函数 DDL 里的implemented by method那段看跳转能不能到对应类。跳转成功才说明名字对上了。如果类存在但还没激活跳转可能不成功这时候我直接看源码字符串里的类名手工比对一次最踏实。还要提一个容易踩的坑AMDP 类的方法如果在类定义里声明为CLASS-METHODS但实现类没有实现IF_AMDP_MARKER_HDB表函数激活时会报“类不是 AMDP 实现类”。这种情况代码上很难一眼看出来检查接口是第一步。4.2 方法签名不匹配导出参数类型必须是表函数名表函数激活时系统会拿 CDS 的returns和 AMDP 方法的导出参数做匹配。匹配规则有两点一是导出参数类型必须是这个表函数名本身不能拿一个结构或者自定义表类型来顶二是方法里导入参数的名字和类型要和表函数的with parameters对得上。我踩过最蠢的坑是导入参数在 XCO 里叫IV_LANGU到了 AMDP 方法实现里手滑写成了IV_LANGSQLScript 编译直接报 unknown column。这类问题不是差一个字母是差一个字母加一个调试周期。解决的办法很简单生成器里参数名统一从配置表取生成表函数和生成 AMDP 实现体都用同一个配置源两边永远是同步的。4.3 权限、传输请求和 XCO 版本兼容问题XCO 生成对象需要完整的开发权限S_DEVELOP、仓库权限、传输请求权限都在必须列表里。在 S/4 本地环境还要求当前用户能写包否则 create 时会被 ABAP Development Tools 的对象锁定机制卡住。报错一般直接体现在异常里把cx_xco系列异常的消息抽出来看比查日志快得多。版本兼容主要集中在两点一是xco_cp_abap_objectsclass这个入口在不同版本对 AMDP 类内部处理的支持程度不同二是表函数的implemented_by这类方法名可能微调。我的建议是写生成器之前先用一个最小的例子跑通当前版本的 XCO API把所有方法名确认清楚再进批量。一次跑几十个对象中间卡 API 是最亏的。4.4 批量生成时的命名、缓冲和日志批量生成场景下我吃过不少亏整理成几条经验。命名规则必须是参数而不是硬编码。类名、表函数名、字段名都从配置表读取写死常量会导致后期维护成本陡增。我见过同事把字段名写死在循环里业务加一个字段需要改程序而不是改配置这就完全背离了批量生成的初衷。对象缓冲是个隐蔽问题。XCO 生成后ABAP 字典和某些缓存不一定立刻刷新程序里如果马上用新的表函数名做类型声明可能拿到旧缓存。最稳的做法是生成器跑完后让用户执行一次字典同步或重新登录而不是在生成器内部紧接着做类型解析。批量日志必须有。我每个对象生成后都记结果成功、失败、原因、激活状态。一张表函数一行日志最后交一个清单给测试比逐个问她“你那个能查出来吗”省很多事。日志里我最看重的字段是传输请求号后续释放、回溯都靠它。4.5 一张速查表收拢高频故障为了加快排查我把最常遇到的问题整理成速查表项目上有人报错就对照这个表基本能定位八成问题。现象原因处理方式表函数激活报实现类不存在类未先生成或类名写错确认类存在检查implemented_by指向类激活报类型不存在表函数还没激活DDIC 类型未生效先激活表函数再回头激活类激活报“类不是 AMDP 类”缺少IF_AMDP_MARKER_HDB接口类定义里补接口方法签名不匹配导出参数类型不是表函数名或参数名拼写不一致用配置表统一参数命名SQLScript 编译报 unknown columnAMDP 方法体里字段名或参数名写错对照表函数returns和with parameterscreate 报权限错误缺少开发或传输请求权限检查S_DEVELOP和传输授权SE80 看不到生成对象对象未激活或还在传输请求里打开请求查看未激活对象批量激活同名对象已存在重复生成前没检查生成前先检查存在性再决定 create 还是 delete 重建5. 实战经验与扩展方向5.1 生产中先跑通 1 个再跑 50 个如果读完前面你准备直接抄代码上生产我拦一下。正确节奏是先选定一个最简单的场景手工配好一个 AMDP 类和一张表函数确认能查询、能被 OData 消费然后写一个最小 XCO 程序只生成这一个场景对应的类和时间函数跑通之后再扩展成批量循环接配置表。这个 1 到 N 的顺序我每次都在用理由很简单XCO 生成链路长任何一个 API 细节都可能有问题1 个都跑不通的情况下50 个只会放大问题。我另外会把生成器程序本身放在一个独立的开发类里加上输出界面。因为生成器也是要维护的业务字段变了要改的是生成器参数而不是一个一个去 SE80 改对象。这类生成器代码写得好后期维护成本很低。5.2 XCO 还能扩展到哪些对象表函数和 AMDP 类只是 XCO 能力的一部分。同一条思路你可以继续做这些事。普通 CDS View 的批量生成用xco_cp_cdsview字段、关联、注解都能模型化。Abstract Entity 也支持这在 RAP 开发里很实用。数据库表和索引有XCO_CP_DBT相关入口ABAP 的类、接口、程序属于xco_cp_abap_objects。也就是说从数据库对象到应用对象只要是你每个项目都要“重复建”的都可以往配置驱动的生成器里放。我自己的下一步想把现有生成器改成只读配置表加可视化界面的版本让业务顾问往配置里加数据开发跑一次生成器导出整个报表层。这样从 XCO 入门到落地整个链路就完全自动化了。最后啰嗦一句我个人的体会XCO 这种模型化创建方式的真正价值不只在于少点点鼠标而在于把开发对象的定义从“手工状态”变成了“代码状态”。对象长什么样、依赖什么、放哪个请求全部记录在生成器里团队协作时看代码就能知道整套数据结构是怎么来的。这个视角比会调几个 API 重要得多。
返回列表