news 2026/1/10 8:25:32

CDS Association详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CDS Association详解

1、 Association 的核心概念

1.1、 什么是 Association?

定义: Association 是 CDS 中一种声明性的语句,用于定义两个 CDS 实体(视图或表)之间的关系。

本质: 它本身不是一条 JOIN 语句,而是一个元数据定义,描述了“如何连接”的关系。只有在被显式使用时(例如通过路径表达式),它才会在生成的 SQL 中触发实际的 JOIN 操作。

1.2、 为什么需要 Association?

抽象性: 将关系逻辑从查询中分离出来,封装在模型内部,提高复用性和可维护性。

语义丰富: 为数据模型赋予业务含义(如“销售订单属于一个客户”)。

便捷性: 通过简洁的路径表达式访问关联数据,无需编写复杂的 JOIN。

框架集成: 是 SAP Fiori Elements 和 OData 服务自动提供导航功能的基础。

1.3、 基本语法

association[min..max] to TargetEntity as _Alias on $projection.SourceField = _Alias.TargetField

[min..max]基数,定义关联的维度。

[0..1]: 目标实体最多有一条记录与之对应。

[1..1]: 目标实体必须有一条记录与之对应。

[0..*][*]: 目标实体有零条或多条记录与之对应。

[1..*]: 目标实体有一条或多条记录与之对应。

TargetEntity: 要关联的目标 CDS 视图或数据库表。

_Alias: 关联的别名,通常以下划线_开头以区分于普通字段。

on ...: 关联条件,类似于 JOIN 的 ON 子句。$projection代表当前视图的字段。


2、Association 的用法与场景

为了进行对比,我们将使用以下简单的数据模型:

YBINV_CUSTOMER: 客户主数据视图

YBINV_ORDER: 销售订单抬头视图

2.1、Association定义与路径表达式

目标: 在订单抬头视图中,关联到客户主数据,以获取客户名称。

2.1.1、定义 Association

// 客户主数据视图 defineview YBINV_CUSTOMER asselect key kunnr, land1, name1 from kna1 // 销售订单头视图 defineview YBINV_ORDER asselect vbeln, auart, kunnr, _customer from vbak // 定义到客户的关联 association[1..1] to YBINV_CUSTOMER as _customer on $projection.kunnr = _customer.kunnr

关键点

此处使用Association关联客户,并定义了一个别名_customer

基数[1..1]表示每个订单必须对应一个客户。

关联条件使用$projection引用vbak的字段。

2.1.2、使用路径表达式消费 Association

仅定义 Association 不会在结果中看到任何信息。必须在select列表中显式消费它。

// 消费 Association 的视图 defineview YBINV_ORDER_CUSTOMER asselect vbeln, auart, kunnr, // 使用路径表达式将关联实体的字段“带”过来 _customer.name1, // 获取客户名称 _customer.land1 // 获取客户国家 from YBINV_ORDER

生成的 SQL 等价于

SELECT so.vbeln, so.auart, so.kunnr, cust.name1, cust.land1 FROM VBAK AS so LEFT OUTER JOIN KNA1 AS cust ON so.kunnr = cust.kunnr

执行结果

2.2、内外连接

基数只定义了表的关系,但不影响最终生成的SQL连接。
比如定义了
association[1..1] to vbap as _item on $projection.vbeln = _item.vbeln
但是系统执行查询时,仍然生成的是左连接


因为系统默认采用left outer join 进行关联。
如果要使用内连接(INNER JOIN),需要特殊处理。比如在查询时,加入限制

defineview YBINV_ORDER_CUSTOMER asselect vbeln, auart, kunnr, _item[inner].posnr, //强制使用inner连接 _item.kwmeng from YBINV_ORDER

2.3、过滤 Association

在消费时,对 Association 指向的目标实体进行过滤。

比如,我们只想获取德国的客户信息。

defineview YBINV_ORDER_CUSTOMER asselect vbeln, auart, kunnr, // 使用路径表达式将关联实体的字段“带”过来 _customer[land1 ='DE'].name1, // 获取德国客户名称 _customer.land1 // 这个仍然是原始的客户国家 from YBINV_ORDER

结果对比

订单ID

客户名称

客户国家

客户名称

SO001

ABC

DE

ABC

SO002

XYZ Corp

US

NULL

SO003

German

DE

German

过滤只影响通过该路径表达式获取的值,不会影响主结果集的行数,也不会影响其他使用同一 Association 的路径。


2.4、暴露 Association 用于导航

在 OData 服务中,我们经常希望将 Association 本身暴露为一个导航链接(Navigation Link),而不是直接展开其字段。

在 OData 元数据中定义/YBINV_UI_CUSTOMER_CDS/YBINV_UI_CUSTOMER('SO001')/toCustomer这样的导航属性。

@OData.publish: true defineview YBINV_UI_CUSTOMER asselect key vbeln, auart, kunnr, // 直接暴露 Association,而不是其字段 // 这将在 OData 元数据中生成一个名为 `toCustomer` 的导航属性 其中'to'是OData自动添加的导航前缀 _customer as Customer from YBINV_ORDER

结果与对比

  • 不暴露 Association

    : OData 服务只有平铺的字段(如name1,land1)。

  • 暴露 Association

    : OData 服务包含一个导航链接。客户端可以通过以下方式访问关联数据:

    • GET /YBINV_UI_CUSTOMER('SO001')?$expand=toCustomer
    • GET /YBINV_UI_CUSTOMER('SO001')/toCustomer
  • 404错误

    :对于导航连接,有时系统会自动添加前缀,比如to,所以如果访问报404错误,需要看/$metadata文件中<NavigationProperty Name="toCustomer">对应的name值。

这种方式提供了更大的灵活性,允许客户端决定是否需要以及何时需要加载关联数据。


2.5、使用$projection进行自关联

当关联条件依赖于当前视图中的计算字段或筛选后的结果时,需要使用$projection

比如:当客户为空时,默认一个客户,需要使用case when语法得出计算后的字段ZKUNNR,然后通过ZKUNNR查询对应的客户主数据:

defineview YBINV_ORDER asselect key vbeln, auart, case when kunnr isnull then'0000300022'//如果没有客户就默认客户编码 else kunnr endas zkunnr, _customer, _item from vbak // 定义到客户的关联 此处使用处理过的字段关联 association [0..1] to YBINV_CUSTOMER as _customer on $projection.zkunnr = _customer.kunnr

$projection确保了关联条件是在当前视图的上下文(包括可能的计算和过滤)中进行的。


3、JOIN和Association的对比

通过以下几点,对两种方法进行深度对比

3.1、灵活性与过度获取数据

JOIN 封装的问题

// 基础视图已经固定返回所有客户字段 defineview I_SalesOrderHeaderWithCustomer asselectfrom snwd_so innerjoin snwd_bpa {...} { salesorder_id, gross_amount, customer_name, // 总是返回 customer_country, // 总是返回 customer_city, // 总是返回 customer_phone, // 总是返回 customer_email // 总是返回 };

使用 Association 的灵活性

defineview I_SalesOrderHeader asselectfrom snwd_so { key salesorder_id, gross_amount, currency_code, _customer from vbak // 定义到客户的关联 association[1..1] to I_Customer as _customer on $projection.customer_guid = _Customer.customer_id // 消费视图1:只需要客户名称 defineview ZC_SimpleOrder asselectfrom I_SalesOrderHeader { salesorder_id, gross_amount, _Customer.customer_name // 只获取需要的字段 }; // 消费视图2:不需要客户信息 defineview ZC_FinancialReport asselectfrom I_SalesOrderHeader { salesorder_id, gross_amount, currency_code // 不消费 _Customer,不会产生 JOIN //_Customer.customer_name };

对比结果

场景

JOIN 封装

Association

只需要1个客户字段

仍然获取所有客户字段

只获取需要的字段

不需要客户数据

仍然执行 JOIN

不执行 JOIN

需要不同客户字段组合

需要创建多个基础视图

单一基础视图满足所有需求

3.2、维护成本

当客户模型变化时:

JOIN 封装方式

// 客户表新增了重要字段 altertable snwd_bpa add company_size nvarchar(20); // 需要修改所有封装了JOIN的基础视图 defineview I_SalesOrderHeaderWithCustomer asselectfrom ... { ..., _customer.company_size // 必须手动添加 }; // 所有基于这个视图的消费视图都能看到新字段(可能不需要)

Association 方式

// 只需要在 YBINV_CUSTOMER视图中暴露新字段 defineview I_Customer asselectfrom snwd_bpa { ..., company_size // 在源头添加 }; // 消费视图按需决定是否使用新字段 // 不需要修改任何现有的消费视图

3.3、多重关系处理

复杂场景:订单有创建者、修改者、销售员等多个人员关联

JOIN 封装的困境

defineview I_SalesOrderHeaderWithAllJoins asselectfrom snwd_so innerjoin snwd_bpa as _customer ... leftjoin snwd_emp as _creator ... leftjoin snwd_emp as _salesperson ... leftjoin snwd_bpa as _bill_to_party ... { salesorder_id, _customer.customer_name, _creator.employee_name as creator_name, _salesperson.employee_name as salesperson_name, _bill_to_party.formatted_name as bill_to_name // 字段爆炸,命名冲突等风险 };

Association 的清晰方案

defineview I_SalesOrderHeader asselectfrom snwd_so { key salesorder_id, association [1..1] to I_Customer as _Customer ..., association [0..1] to I_Employee as _CreatedBy ..., association [0..1] to I_Employee as _SalesPerson ..., association [0..1] to I_Customer as _BillToParty ... }; // 消费时 按需选择 defineview ZC_OrderForSales asselectfrom I_SalesOrderHeader { salesorder_id, _Customer.customer_name, _SalesPerson.employee_name // 只关心销售相关 }; defineview ZC_OrderForBilling asselectfrom I_SalesOrderHeader { salesorder_id, _BillToParty.customer_name // 只关心账单相关 };

3.4、框架集成与语义化

Association 的独特优势

3.4.1、OData 导航

@OData.publish: true defineview ZC_SalesOrderForOData asselectfrom I_SalesOrderHeader { key salesorder_id, gross_amount, _Customer as ToCustomer // 自动生成导航属性 };

客户端可以调用:/SalesOrders('123')/ToCustomer

3.4.2、文本关联

defineview YBINV_ORDER asselectfrom vbak // 定义到客户的关联 association [1..1] to YBINV_CUSTOMER as _customer on $projection.kunnr = _customer.kunnr { key vbeln, auart, @ObjectModel.text.element: ['name'] //绑定name和kunnr kunnr, @Semantics.text: true _customer.name, //将name作为描述 _customer }

在消费视图中定义

@OData.publish: true defineview YBINV_UI_CUSTOMER asselect key vbeln, auart, @ObjectModel.text.association: '_customer'//在客户中展示描述 kunnr, _customer.name, _customer as Customer from YBINV_ORDER

UI 自动显示客户名称

3.4.3、搜索帮助

Value Help注解@Consumption.valueHelpDefinition

defineview ZC_SalesOrder asselectfrom I_SalesOrderHeader { @Consumption.valueHelpDefinition: [{ entity: { name: 'I_Customer', element: 'customer_guid' } }] customer_guid, _Customer.customer_name };

3.5、性能对比

实际执行计划分析

JOIN 封装视图
消费时,该视图已经执行了完整的JOIN。

Association 视图
消费时,优化器可能将路径表达式重写为高效的JOIN,只获取需要的字段。

4、 总结与最佳实践

特性

传统 SQL JOIN

CDS Association

本质

命令式,是查询的一部分

声明式,是模型元数据的一部分

复用性

差,JOIN 逻辑在每个查询中重复

极佳

,定义一次,随处消费

可读性

复杂查询可读性差

路径表达式

使查询意图更清晰

灵活性

直接控制 JOIN 类型和条件

通过基数过滤间接控制,更抽象

框架集成

无特殊支持

深度集成

,是 Fiori Elements 和 OData 导航的基石

耦合

查询与关系逻辑紧耦合

查询与关系逻辑解耦,模型更清晰

最佳实践

始终使用 Association: 在新的 CDS 开发中,优先使用 Association 而不是直接 JOIN。

明确的别名: 使用下划线_开头为 Association 命名,以示区分。

用于文本关联: 结合@ObjectModel.text.association注解,自动为代码字段提供描述文本。

OData 导航: 通过暴露 Association 来构建丰富的、可导航的 OData 服务。

以上就是关于Association的介绍,希望对你有所帮助

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/8 8:11:06

3、Qt 界面开发:小部件与布局全解析

Qt 界面开发:小部件与布局全解析 1. 布局与小部件基础 所有图形用户界面(GUI)都是围绕小部件(Widgets)构建的,这些小部件通过布局(Layouts)进行排列。布局在 Qt 中非常重要,它能让对话框适应屏幕分辨率、字体大小和不同语言的变化。与之相对的静态布局,需要为每个小…

作者头像 李华
网站建设 2025/12/27 3:38:46

6、Qt 自定义小部件开发全解析

Qt 自定义小部件开发全解析 1. 小部件概述 在应用程序开发中,小部件(Widgets)指的是构成应用程序的各种可视化元素,如按钮、标题栏、文本框、复选框等。在使用小部件创建用户界面时,存在两种思路:一是坚持使用标准小部件,二是大胆创新创建自己的小部件,Qt 对这两种方…

作者头像 李华
网站建设 2025/12/28 16:02:34

Spring AI 最新实战系列(一)完成一个简单的AI项目

使用前介绍 我们以 Alibaba 的百炼平台作为Spring-AI的模型讲解&#xff0c;以最新稳定版作为架构。 spring-ai 的最新版本 1.1.2 &#xff1b;alibaba-spring-ai 的最新版本 1.1.0.0-RC1。 需要注意一点&#xff1a;最新版本的 Spring Boot 4.0.0 不能适配&#xff0c;需要降低…

作者头像 李华
网站建设 2026/1/5 12:39:49

LobeChat智谱ChatGLM接入全流程:Zhipu AI API对接

LobeChat 智谱 ChatGLM 接入全流程&#xff1a;Zhipu AI API 对接 在智能对话系统快速普及的今天&#xff0c;越来越多企业和开发者希望构建既具备专业能力又符合本地化需求的 AI 助手。然而&#xff0c;直接使用境外大模型服务常面临中文表达生硬、数据出境合规风险、网络延迟…

作者头像 李华
网站建设 2026/1/8 5:18:14

EmotiVoice能否实现语音情感渐变过渡?动态控制探索

EmotiVoice能否实现语音情感渐变过渡&#xff1f;动态控制探索 在虚拟偶像直播中&#xff0c;一个角色从担忧到释然的语气转变&#xff0c;往往只需一句话的时间&#xff1b;在互动游戏中&#xff0c;NPC因玩家行为瞬间由温和转为愤怒——这些细腻的情感流动&#xff0c;早已超…

作者头像 李华
网站建设 2026/1/3 21:59:55

终极微博备份指南:Speechless免费工具完整使用教程

终极微博备份指南&#xff1a;Speechless免费工具完整使用教程 【免费下载链接】Speechless 把新浪微博的内容&#xff0c;导出成 PDF 文件进行备份的 Chrome Extension。 项目地址: https://gitcode.com/gh_mirrors/sp/Speechless 在信息碎片化的今天&#xff0c;微博承…

作者头像 李华