在S/4HANA 2020之后的项目里,"把经典CDS视图改成CDS View Entity"几乎是每个数据模型改造任务单里都要出现的动作。我去年接手的一个报表改造项目里,有三十多个基于DEFINE VIEW的老视图,对应着二十多个ALV报表和四五个下游接口,全部都要平移到DEFINE VIEW ENTITY上去。最开始我以为只是把define view改成define view entity、删掉@AbapCatalog.sqlViewName就完事,真动起手来才发现:语法只是一小部分,底层存储对象变了、权限控制方式变了、各种"直接读数据库底层视图"的历史习惯全暴露出来了。
这篇文章把整个迁移过程拆开讲,包括为什么要迁、两种语法到底差在哪、ADT向导怎么用、报表侧如何配合改造,以及我踩过的十几个坑。内容以S/4HANA 2020之后、ABAP开发工具ADT为背景,适合正在做CDS迁移或准备把老视图升级的ABAP开发同学参考。整篇没有太多官话,都是实际敲过代码、跑过回归后的经验总结。
1. 为什么非迁不可:CDS View Entity 不是在旧语法上打补丁
1.1 旧语法的历史包袱
CDS在NetWeaver 7.4时代刚进入ABAP时,为了能和传统ABAP Dictionary、SE11、数据库视图机制和平共处,每个经典CDS View都必须通过@AbapCatalog.sqlViewName关联一个底层数据库视图。也就是说,你写了一个define view,系统实际上会在ABAP字典里维护一个CDS对象,又要在数据库层创建一个SQL View,两套对象绑定在一起才能工作。
@AbapCatalog.sqlViewName: 'ZMARA_LEGACY' @AbapCatalog.compiler.compareFilter: true @AccessControl.authorizationCheck: #CHECK @EndUserText.label: '物料主数据经典视图' define view ZI_MARA_LEGACY as select from mara left outer join t023 as _matkl on _matkl.matkl = mara.matkl { mara.matnr as MaterialNumber, mara.ersda as CreationDate, mara.matkl as MaterialGroup, _matkl.wgbez as GroupDescription } where mara.mtart = 'ROH';这段写法在S/4HANA 2020之前的时代非常好用:SE11能看到、底层SQL能直接访问、RFC还能导数据。但它的"双份对象"状态在新平台上成了负担。最典型的问题有三个:
- 每次对CDS View做字段调整,底层SQL View也要跟着同步,激活链路更长;
- 权限控制要在CDS层理解一遍,又要和数据库层的授权对象纠缠一遍;
- 在ABAP云环境(BTP/Steampunk)里,底层SQL View被彻底禁止,SAP根本不给这种双对象模型留活路。
我接触过的一个客户系统里甚至出现过这种情况:SE11里的数据库视图被人手动改了权限,导致CDS层明明已经做了@AccessControl.authorizationCheck: #CHECK,但实际报表返回的数据还是和学生账号看到的权限结果不一致,排查了大半天才发现问题出在数据库层的GRANT语句上。这种历史遗留问题,在迁移到View Entity之后反而能一劳永逸地避开。
1.2 新语法带来的实际收益
define view entity是CDS的现代形态,直接在ABAP层创建"视图实体",不再生成对应的数据库SQL View。表面看只是少了一行sqlViewName注解,实际上整个对象模型简化了:
- 少掉一层DB View不意味着功能变弱,反而让ABAP编译器对视图的定义解析更直接;
key字段被强制要求,数据模型的完整性更好,下游做OData、做分析查询都有明确主键可依赖;- 新函数、新注解、参数化视图、关联导航等能力,基本都以View Entity为基准在持续演进;
- 云版本、S/4HANA 2020之后的新项目,官方默认推荐的就是View Entity。
性能方面我不想神化它。我实测过一个三层视图嵌套的场景:最底层是几张业务表,中间两个经典CDS视图互相引用,最上层是一个报表直接查这个视图。迁移到View Entity之后,HANA的执行计划确实简化了,原因是少了一层SQL View的名称解析和语义映射。但在单层视图、简单过滤的场景下,新旧差异感知不强。所以我的建议很明确:迁移的核心驱动力是平台的演进方向,而不是追逐一个虚无缥缈的性能数字。
2. DEFINE VIEW 与 VIEW ENTITY 的语法位移:一行一行看差异
2.1 两种语法骨架的直观对比
最直观的方式是并排看。上面那个经典CDS视图,迁移成View Entity之后长这样:
@AccessControl.authorizationCheck: #CHECK @EndUserText.label: '物料主数据视图实体' define view entity ZI_MARA_ENTITY as select from mara left outer join t023 as _matkl on _matkl.matkl = mara.matkl { key mara.matnr as MaterialNumber, mara.ersda as CreationDate, mara.matkl as MaterialGroup, _matkl.wgbez as GroupDescription } where mara.mtart = 'ROH';从字符层面看,改动点只有三处:define view变成define view entity、删掉@AbapCatalog.sqlViewName和@AbapCatalog.compiler.compareFilter、给主数据字段加上key。但就是这三处改动,把整个对象在ABAP运行时中的角色完全改变了。
2.2 被删除的注解意味着什么
先看@AbapCatalog.sqlViewName。老视图里,这个注解负责把CDS视图映射到数据库视图,映射关系建立之后,你在SE11或者其他数据库工具里能看到一个真实存在的SQL View。别以为这只是个"名字",它牵扯到:
- SE11里能够直接浏览的数据对象;
- RFC或其他外部系统通过数据库连接直接读取的路径;
- DBA在数据库层做的授权管理对象。
迁移到View Entity之后,这个数据库视图就不存在了。它的替代路径非常清晰:所有读取都必须通过ABAP层的Open SQL、CDS消费方或者OData服务完成。如果你的系统里存在"第三方工具直连数据库读SQL View"的情况,请务必在迁移前排查干净,否则上线当天就是断供当天。
@AbapCatalog.compiler.compareFilter也一样。这个注解在旧编译器体系里影响过滤条件的比较优化,在新Entity语法中已经没有对应概念。ADT向导处理的方式通常是直接丢弃。我建议不要心疼这个注解——它被丢弃后,如果查询计划出现明显变化,优先检查WHERE条件能否改写得更紧凑,而不是试图在新语法里找回一个等价的开关。
2.3 key 字段为什么绕不开
经典CDS View其实不强制key。你可以写一个没有任何key字段的投影,系统也能激活,下游查询也能跑。但View Entity不同,它要求投影出来的字段里至少有一个主键标记,理由很直接:
- ABAP和HANA需要依赖key做数据去重、做实体识别;
- OData服务、ODP提取、分析引擎都需要稳定主键;
- 后续在这个entity上做
association、做redirected to,key一致才能保证导航正确。
我见过一个小白项目,迁移时开发图省事,把老视图里所有字段自动勾成key,结果下游报表用SELECT *拉数据,突然跑出来大量重复行——因为源表本身不是按这些字段唯一的,多个业务主键被同时标记成key之后,语义完全变了。正确做法是把key留给源表的真正主键字段。比如我们项目里,物料主数据就只留matnr作为key,即使它实际在某个JOIN场景下出现重复,也至少和源表语义保持一致。
2.4 表达式和函数的兼容性细节
表达式和函数在迁移过程中是第二大类差异点。我遇到过的典型情况:
- 老视图里某些字段做了
cast,比如把matkl转成字符串或者把menge转成d34r,迁移后编译器对类型推断更严格,原先"能过"的隐式转换现在可能直接报Assignment to incompatible data type; - 聚合字段和
group by的配合:老视图里sum出来的字段在group by之外被引用,迁移前编译器睁一只眼闭一只眼,迁移后会被卡住; - 如果投影里有
case when分支,各分支的返回类型尽量显式统一。新语法对分支类型的一致性要求更苛刻,尤其是返回CHAR和NUMC混用时,一定用cast拉齐。
我的建议是:转换之后第一次激活,不要急着冲报表,先把所有字段的数据类型、精度、key标记抄录下来,和旧视图做一次逐字段对照。这一步能省掉后面成堆的隐性BUG。
3. 用 ADT 向导完成转换:从右键到重新激活的每一步
3.1 转换前必须准备好的三件事
我不建议拿到ADT就直接右键转换。磨刀不误砍柴工,以下三件事先做完:
确认系统版本和ADT版本。View Entity迁移在S/4HANA 2020(ABAP 1909)之后才被完整支持。如果你是2020之前的版本,先别谈迁移,老老实实升级或评估兼容性。ADT方面,我环境里用的是2023年的版本,右键菜单中能找到相关入口;旧版ADT可能功能不全,建议检查Help菜单里的CDS功能列表。
梳理下游引用清单。你要知道这个CDS View被哪些上层视图引用、被哪些报表直接
SELECT FROM、有没有OData服务在消费它、有没有外部程序通过@AbapCatalog.sqlViewName对应的DB View直接读取。梳理方法很简单:ADT里选中DDLS源,右键Where Used List,再全库搜索一遍sqlViewName对应的数据库视图名。做好旧版本备份。虽然ADT带Undo和本地历史,但双击确认一下Git仓库状态或手动导出一份DDLS源文件,成本很低。我见过不止一次转换向导中途报错,把源文件改到一半,结果两边都不好激活的尴尬场景。
3.2 向导操作流程
以我手头的ADT版本为例,操作大概是这样的。在Project Explorer中选中要转换的DDLS源,右键菜单的Refactor子菜单里能找到Convert to CDS View Entity相关入口。不同版本的菜单位置会有差异,有的版本在右键菜单直接展示,有的需要在Source菜单里翻一下,但核心流程一致:
- 向导会先做一次静态检查,列出所有潜在的不兼容项,比如缺失key、不支持的注解、表达式类型冲突;
- 检查结果出来后,可以选择单个对象或多个对象一起转换。项目里如果是一批视图批量导,建议全选,但注意分批执行,每批控制在十几个以内比较好排查问题;
- 转换过程中,向导会自动把语法从
define view改成define view entity,去掉不再支持的注解,并对key字段给出默认建议; - 最后生成一个新的DDLS源,进入激活阶段。激活时如果报错,错误列表会直接指向出问题的行。
如果你用ADT找不到这个入口,可以搜索 "CDS View Entity Migration" 相关帮助文档。还有一个土办法:手动新建一个DDLS源,选择define view entity模板,然后把老视图的select body贴进去,再手工修类型和key。虽然费点劲,但对理解语法差异很有帮助,特别适合本地没有向导版本的情况。
3.3 转换完成后必须人工复查的点
向导能帮的忙就到语法层为止,下面这些点它管不了,必须人工过一遍:
- key字段的合理性。自动标记可能猜错,尤其在有JOIN、有聚合、有
union的场景下; - 注解变动对下游的影响。比如
@EndUserText.label、@UI.lineItem、@ObjectModel.*这些注解如果在新语法里表现不一致,OData服务和前端字段标签都可能变; - 关联关系是否完整。如果老视图里定义了association,迁移后目标entity是否还存在、关联字段的key是否保持对齐;
- 旧对象的释放策略。如果entity用了新名字,旧view是立刻删除还是保留过渡期,需要和运维评估。过渡期双跑是常见做法,但别拖太久,否则维护成本翻倍。
4. 报表侧的实际适配:调用方代码和平行测试方案
4.1 改报表代码的第一刀:数据源切换
报表侧的改动通常没有想象中那么大。老代码里凡是SELECT ... FROM zi_mara_legacy这种写法,直接把数据源名换成entity名即可。如果entity名沿用旧view的名字,甚至在传输到生产后一行代码都不用改。这也是我强烈建议命名尽量平滑的原因:语义没有变化时,别为了"焕然一新"改名,给自己找一堆下游修改的活。
如果老报表喜欢SELECT *,迁移后字段集合基本保持一致。但要注意INTO CORRESPONDING FIELDS OF的场景:如果entity的key字段、字段顺序发生调整,结构传输可能不完整,最好的规避方式是INTO CORRESPONDING FIELDS OF之前先看一遍entity的字段清单,别指望通配符永远替你兜底。
4.2 JOIN 与关联导航的取舍
我见过很多老报表,为了拉取关联表的描述文本,在ABAP层写了好几个LEFT JOIN t023 ON ...、LEFT JOIN t024 ON ...。迁到view entity之后,这些JOIN可以保留,毕竟语法兼容。但既然已经动刀了,我建议顺手评估一下哪些JOIN可以收进entity的association里,让ABAP报表侧的SQL更清爽。
举个例子,原来报表里要取物料组描述,代码大概是:
SELECT mara.materialnumber, mara.materialgroup, t.wgbez AS groupdesc FROM zi_mara_entity AS mara LEFT OUTER JOIN t023 AS t ON t.matkl = mara.materialgroup INTO TABLE @DATA(lt_result) UP TO 10 ROWS.如果你的entity已经在CDS层定义好了到物料组的association,报表里通过关联导航就能少写一段ON条件。这种改写对代码可读性提升非常明显,而且关联的解析和join顺序由ABAP编译器统一处理,出错的概率更小。当然,改不改取决于团队约定,不要为了炫技把简单查询改复杂。
4.3 数据一致性平行测试方案
迁移不是激活就算完,数据一致性必须验证。我用的方案比较简单,但很有效:
- 行数级校验。旧view和entity分别执行
COUNT(*),比对结果是否一致; - 数值汇总校验。挑几个业务金额字段做
SUM,最好跨几个过滤条件做几组; - 明细抽样校验。对关键凭证号、物料号做点查,比对每一列的值是否逐字一致;
- 异常值校验。重点关注CHAR类型字段的尾部空格、日期字段的
00000000这类边界值。
每个环节我都用一个传输请求跑一套比对报表,数据不匹配就停下来查原因。这种测试方案虽然土,但能覆盖掉迁移过程中绝大多数隐藏的语义差异。
5. 迁移后的避坑清单:十个真实踩坑点
5.1 坑一:DCL授权检查在激活时突然冒出来
View Entity默认对权限控制的检查方式和老view不完全一样。如果你在老view上定义了@AccessControl.authorizationCheck: #CHECK,却没有配套的Access Control文件(DCL),激活时很可能直接报授权缺失。见过新手一看到这个报错就慌,其实解决办法就是去ADT里给entity创建一个对应的Access Control源,把原本的授权条件搬过去。注意在搬的过程中,如果老view用的是#NOT_REQUIRED,新entity里可以保留相同的注解值,但生产环境强烈建议按#CHECK补全DCL,否则数据权限裸奔。
5.2 坑二:底层SQL View断供的连锁反应
这是最大的暗雷。一个老view在系统里运行了五年,@AbapCatalog.sqlViewName对应的DB View早就被各种程序、定时任务、第三方工具引用。迁移成entity之后,那个DB View很可能直接消失。我们项目里就有一个外围数据抽取作业,通过DB连接直连ZMARA_LEGACY这个视图拉数,迁移次日凌晨作业进程报"表不存在",监控群直接报警。
所以迁移前一定要做一次数据库视图引用排查:SE11里搜视图名、ABAP代码里搜 "FROM ZMARA_LEGACY"、数据库工具里看依赖关系。确认没有外部引用再动手,否则就保留一个兼容view作为过渡,等外部程序切换完再清理。
5.3 坑三:key字段选错导致重复数据
前面提到过,自动标记的key不一定是源表主键。我见过一个跨多个表JOIN的宽表视图,向导把所有投影字段都标成了key,结果下游报表用SELECT *一拉,原本应该按订单号聚合的行,被拆成了若干重复行。解决方法是回归到业务语义:key就是数据模型里识别一条记录的那几个字段,多数情况就是源表主键。如果视图本身就是聚合结果,没有任何自然主键,那就选分组字段作为key,至少逻辑上能表达"这个视图里一行代表什么"。
5.4 坑四:金额和单位注解丢失后,报表格式全乱
经典CDS view里,如果投影了货币金额字段,老系统可能依靠隐式转换维持着CURR和CURRENCY KEY的关系。迁移到view entity后,如果没有显式加上@Semantics.amount.currencyCode这类注解,报表取出来的金额会变成"裸数字",小数位和货币显示完全看前端心情。这个坑非常隐蔽,因为数据值本身没变,但ALV显示格式变得非常奇怪。建议迁移后逐字段检查所有CURR、QUAN类型字段,确认语义注解齐全。
5.5 坑五:CHAR类型尾部空格的变化
HANA上CHAR字段的尾部空格处理本来就是老大难。老view通过DB View读取时,数据库层的填充规则可能把尾部空格补齐;entity直接走ABAP CDS解析后,某些场景下CSTR和CHAR的尾随空格行为会有细微差异。如果平行测试发现某几个字段总是"比对一致但TRUNCATED/填充不一致",优先检查字段的精确类型定义,在ABAP层用CONCAT或LTRIM显式处理。
5.6 坑六:OData服务需要重新激活runtime组件
老view如果被OData服务作为数据源使用,迁移后光激活entity不够,还要去SEGW里检查服务定义,确认数据源引用是否仍然有效。有的场景需要重新生成runtime artifact,有的只需要重新激活服务。别忽略这一步,否则前端App到生产环境一调接口就是500。
5.7 坑七:关联目标同步迁移的时序
如果你迁移的view被另外十多个上层view当作association目标,不要只迁这一个就收工。上层view里如果通过association [0..*] to ZI_MARA_LEGACY引用老对象,老view一旦消失或改名,上层全部激活失败。稳妥做法是从底层叶子视图开始迁,一层一层往上,顺带把每个引用方的语句同步改掉。项目里我习惯先列一张"CDS依赖树",自底向上分批,而不是按字母顺序乱迁。
5.8 坑八:变更传输顺序问题
一个CDS view entity涉及的主DDLS、DCL文件、下游消费view,在传输请求里要保证顺序正确。如果只激活了entity就把传输释放,而DCL和下游引用还没传,目标系统会出现激活断层。项目里我吃过这个亏,后来统一建议:一个迁移unit里,至少把"entity + DCL + 受影响的消费方"打包进同一个请求,或者明确标记依赖顺序。
5.9 坑九:参数视图的参数列表改变
老view如果用了with parameters,迁移后参数列表的写法要保持兼容。不要随手把参数名改了,否则所有调用方都会挂。ADT检查时会给提示,但提示归提示,最终拍板改不改的是人。
5.10 坑十:新旧对象并存期的维护混乱
迁移过程中,新旧对象会有一段并行期。最怕的是大家随手修数据、补字段,一会儿改旧的,一会儿改新的,最后两边语义漂移,谁也说不清哪个是准的。建议在并行期定一个强硬规则:新需求一律改entity,旧view只做业务维护不做新功能;并行期超过一个季度还未完成迁移的,重新评估风险。
这个清单不算穷尽,但基本覆盖了我迁移三十多个视图过程中遇到的大部分"惊喜"。每一类坑在测试环境都能模拟出来,代价是走一遍测试计划,千万别跳步。
6. 最后聊两句实操节奏
如果让我总结整个CDS View Entity迁移项目的最核心经验,就一句话:技术难点不深,管理难点在协调。逐个改语法、调注解,半天能搞定几个;真正费时间的是下游引用梳理、外部系统配合、数据一致性比对。我建议把迁移节奏拉得优雅一点,按依赖树分层推进,每一层做完就做一轮报表回归,达到可发布状态再动下一层。毕竟这种迁移不会带来即时可见的巨大性能提升,但它决定了你的系统在下一个平台版本里还能不能顺畅升级。早迁早安心,晚迁攒债。