Como usar os BDEF Derived Types no SAP RAP: %tky, %cid e %control na prática
Por que tantos % num código RAP?
Se você abriu um handler de MODIFY ENTITIES pela primeira vez e se deparou com %cid, %tky, %control, %target e mais meia dúzia de componentes com prefixo %, a reação natural é a mesma de todo mundo: "de onde saiu isso e qual eu uso?".
Esses são os BDEF Derived Types — tipos derivados que o compilador ABAP gera a partir da sua Behavior Definition e da CDS associada. O prefixo % é reservado justamente para que esses componentes nunca colidam com os campos da sua entidade. Entender cada um é o que separa um código RAP que "funciona por sorte" de um que é portátil entre managed/unmanaged, draft/non-draft e early/late numbering.
O essencial em 4 linhas
%tkyé a chave que você deve usar por padrão emUPDATE,DELETEeEXECUTE— ela já carrega%is_drafte%pidquando existem.%cididentifica uma instância recém-criada dentro da request;%cid_refaponta de volta para ela em operações encadeadas.AUTO FILL CIDgera o%cidautomaticamente — mas aí você perde a capacidade de encadear com%cid_ref.%controldecide, campo a campo, o que foi de fato fornecido e o que será gravado.
O que são os componentes %?
São componentes gerados automaticamente a partir do BDEF e da CDS. Alguns são campos simples (%cid, %cid_ref, %is_draft, %pid); outros são grupos que agregam vários campos sob um nome só (%key, %tky, %pky, %data, %control, %param, %target). Eles aparecem nos tipos derivados das operações EML — CREATE, UPDATE, DELETE, EXECUTE e READ — e cada operação só aceita um subconjunto.
Componente | Tipo | Para que serve | Onde aparece |
|---|---|---|---|
%cid | Campo (string) | ID temporário (Content ID) de uma instância recém-criada dentro da request EML | CREATE, CREATE BY _assoc |
%cid_ref | Campo (string) | Referência a um %cid já atribuído na mesma request | CREATE BY _assoc, UPDATE, DELETE, EXECUTE |
%key | Grupo | Chave primária persistente da instância | Todas |
%tky | Grupo | Chave transacional: %key + %pid (late numbering) + %is_draft (draft) | Todas, exceto CREATE raiz |
%pky | Grupo | Chave preliminar: %key (+ %pid em late numbering). Subconjunto de %tky | UPDATE, DELETE, EXECUTE, CREATE BY _assoc |
%data | Grupo | Todos os campos de chave + dados da entidade | CREATE, UPDATE |
%control | Grupo | Flags (ABP_BEHV_FLAG) que indicam quais campos foram providos/devem retornar | CREATE, UPDATE, READ dinâmico, ações with control |
%param | Grupo | Parâmetros de entrada de ações não-padrão e eventos | EXECUTE, eventos |
%target | Grupo | Encapsula chave + dados da entidade-filha em CREATE BY _assoc | CREATE BY _assoc |
%is_draft | Campo | Indicador de instância em draft ('01' draft / '00' ativo) | Cenários com draft |
%pid | Campo | Preliminary ID em cenários de late numbering | Late numbering |
Qual % aparece em cada operação?
Cada operação EML tem um tipo derivado próprio, e cada tipo admite apenas alguns componentes. Tentar usar %target num UPDATE, por exemplo, simplesmente não compila. Esta é a matriz de referência:
Operação | Tipo derivado | Componentes admitidos |
|---|---|---|
CREATE | TABLE FOR CREATE bdef | %cid, %control, %data, %key |
CREATE BY _assoc | TABLE FOR CREATE bdef\_assoc | %cid_ref, %key, %pky, %target, %tky |
UPDATE | TABLE FOR UPDATE bdef | %cid_ref, %control, %data, %key, %pky, %tky |
DELETE | TABLE FOR DELETE bdef | %cid_ref, %key, %pky, %tky |
EXECUTE (action) | TABLE FOR ACTION IMPORT bdef~action | %cid_ref, %key, %param, %pky, %tky |
READ | TABLE FOR READ IMPORT bdef | chaves (%key) e %control (na forma dinâmica) |
%key, %pky e %tky: a confusão número 1 do RAP
Essa é, de longe, a maior fonte de dúvida em projetos RAP. E vale dizer logo: a distinção entre as três é puramente transacional. Não tem nada a ver com o conceito de "semantic key" do CDS (a annotation @ObjectModel.semanticKey) — sobre isso falamos mais adiante.
%key — a chave primária persistente
%key reúne os campos-chave da entidade como definidos na CDS (em geral a UUID, tipo TravelUUID, ou os campos marcados com key no define view). É o identificador único da instância depois de salva no banco. Dentro de %control, esses mesmos campos aparecem com tipo ABP_BEHV_FLAG.
%pky — a preliminary key
%pky = %key + %pid, e o %pid só existe em late numbering. Em early/managed numbering, %pky é equivalente a %key. Ela existe para cenários onde a chave final só é atribuída no método adjust_numbers.
%tky — a transactional key (use esta)
%tky é o identificador transacional completo durante a fase de interação do RAP. Ela contém:
%key(sempre)%pid(em late numbering)%is_draft(em cenários com draft)
Em cenários sem draft e sem late numbering, %tky é estruturalmente idêntica a %key. Mesmo assim, a recomendação oficial é sempre usar %tky. E há três motivos concretos:
Prepara o código para uma futura migração para draft sem retrabalho.
%keysozinha não distingue uma instância ativa de uma instância draft com a mesma chave. Num app com draft, usar%keypode atingir o registro errado.Em respostas
FAILED/REPORTED, atribuições diretaswa-%tky = ...-%tkysão type-compatible; misturar grupos diferentes gera warnings.
A hierarquia de containment
%tky ⊇ %pky ⊇ %key
%tky inclui %is_draft (draft) e %pid (late numbering)
%data ⊇ %key + todos os campos não-chave
%control inclui flags para todos os campos de %dataRegra prática: quando usar cada uma
Cenário | Use |
|---|---|
Identificar instância em UPDATE/DELETE/EXECUTE em qualquer BO | %tky (padrão recomendado) |
Referenciar instância recém-criada na mesma request | %cid_ref |
Identificar entidade-filha em CREATE BY _assoc cujo pai foi recém-criado | %cid_ref no pai + %target nos filhos |
Código legado sem draft e sem late numbering | %key ainda funciona, mas migre para %tky |
Atribuições entre BOs diferentes ou tipos não-RAP | use CORRESPONDING #( ... ) |
Cuidado com o warning de atribuição. wa-%tky = wa-%key gera warning porque %tky pode conter componentes adicionais. Use wa-%tky = CORRESPONDING #( wa-%key ). Já entre componentes idênticos da mesma entidade (wa1-%tky = wa2-%tky), prefira a atribuição direta — CORRESPONDING tem performance pior.
%cid e %cid_ref: o Content ID
%cid é uma string de livre escolha do desenvolvedor que identifica unicamente uma instância dentro de uma request EML. Ela não é persistida. Você a usa quando a chave final ainda não existe (managed/late numbering) ou quando precisa referenciar a instância recém-criada em operações seguintes da mesma MODIFY ENTITIES.
Boa prática: sempre preencha %cid, mesmo que pareça desnecessário. Ele facilita o rastreio em MAPPED/FAILED/REPORTED. Já %cid_ref aparece nos tipos derivados de UPDATE, DELETE, EXECUTE e CREATE BY _assoc para apontar de volta para o %cid definido num CREATE anterior da mesma request.
AUTO FILL CID e as três variantes de sintaxe do CREATE
AUTO FILL CID delega ao runtime do RAP a geração automática do %cid de cada linha criada. O framework preenche internamente com uma string única. O comportamento de %cid e %control muda conforme a sintaxe escolhida:
Sintaxe | %cid | %control | Palavra-chave |
|---|---|---|---|
CREATE FROM itab | você preenche | você preenche explicitamente | FROM |
CREATE AUTO FILL CID WITH itab | runtime preenche | você preenche explicitamente | WITH |
CREATE FIELDS ( f1 f2 ) WITH itab | você preenche | runtime preenche (campos listados = mk-on) | WITH |
Repare: AUTO FILL CID usa WITH (não FROM) para introduzir a tabela. Trocar uma pela outra é um erro de sintaxe comum.
As três variantes, lado a lado:
" Variante 1 — FROM: você cuida de tudo
MODIFY ENTITIES OF zdemo_rap ENTITY root
CREATE FROM VALUE #(
%control = VALUE #( field1 = if_abap_behv=>mk-on
field2 = if_abap_behv=>mk-on )
( %cid = 'c1' field1 = 'A' field2 = 'B' ) )
MAPPED DATA(mapped).
" Variante 2 — AUTO FILL CID: runtime gera %cid, você ainda preenche %control
MODIFY ENTITIES OF zdemo_rap ENTITY root
CREATE AUTO FILL CID WITH VALUE #(
%control = VALUE #( field1 = if_abap_behv=>mk-on
field2 = if_abap_behv=>mk-on )
( field1 = 'A' field2 = 'B' ) " sem %cid
( field1 = 'C' field2 = 'D' ) )
MAPPED DATA(mapped).
" Variante 3 — FIELDS WITH: runtime gera %control, você fornece %cid
MODIFY ENTITIES OF zdemo_rap ENTITY root
CREATE FIELDS ( field1 field2 ) WITH VALUE #(
( %cid = 'c1' field1 = 'A' field2 = 'B' ) )
MAPPED DATA(mapped).A limitação crítica: %cid_ref não referencia %cid auto-gerado
Como o %cid gerado pelo runtime é opaco (você não conhece a string atribuída em tempo de design), o %cid_ref não consegue referenciá-lo. Isso quebra qualquer padrão de encadeamento:
" ❌ NÃO funciona com AUTO FILL CID
MODIFY ENTITIES OF zdemo_rap ENTITY root
CREATE AUTO FILL CID WITH VALUE #(
%control = VALUE #( ... )
( field1 = 'A' field2 = 'B' ) )
UPDATE FIELDS ( field2 ) WITH VALUE #(
( %cid_ref = '???' " você não sabe qual %cid usar
field2 = 'novo' ) ).Quando usar AUTO FILL CID
✅
CREATEstandalone, sem operações encadeadas na mesmaMODIFY.✅ Criação em lote de instâncias independentes, onde só importa o mapeamento final via
MAPPED.✅ Cenários draft com
CREATEpuro, onde a continuidade transacional é cuidada pelo framework de draft.❌ Evite quando há encadeamento
CREATE→UPDATE/DELETE/EXECUTEna mesma request.❌ Evite quando há
CREATE BY _assocreferenciando o pai recém-criado via%cid_ref— nesse caso, forneça o%cidmanualmente.
Mesmo com %cid automático, o MAPPED devolve a correlação %cid ↔ %key:
LOOP AT mapped-root INTO DATA(line).
WRITE: / 'cid auto-gerado:', line-%cid,
/ 'chave persistida:', line-%key-key_field.
ENDLOOP.%control: quem decide o que é salvo
%control é uma estrutura paralela ao %data em que cada campo é do tipo ABP_BEHV_FLAG (x length 1). Ele indica se o campo foi efetivamente fornecido pelo consumer. Use a constante IF_ABAP_BEHV=>MK: mk-on (campo fornecido / deve retornar) e mk-off (não fornecido / não retornar).
Com
FIELDS ( f1 f2 ) WITH ..., o compilador seta%controlautomaticamente para os campos listados.Com
FROM ...(semFIELDS), você tem que preencher%controlexplicitamente.Em
UPDATE, só os campos com%control = mk-onsão atualizados. Campos commk-offmantêm o valor anterior, mesmo que preenchidos na estrutura.Em ações com parâmetros
mandatory:execute, o consumer precisa setar%param-%control-<campo> = mk-on, ou ocorre erro de runtime.
%data, %param e %target: os outros grupos
%data agrupa chaves primárias + todos os campos de dados da entidade. É útil quando você quer manipular o "conteúdo da linha" sem listar campos, e permite acesso polimórfico:
DATA(a) = upd_tab[ 1 ]-key_field. " acesso direto
DATA(b) = upd_tab[ 1 ]-%data-key_field. " via %data
DATA(c) = upd_tab[ 1 ]-%data-%key-key_field. " via %data → %keyGaranta apenas uma atribuição por componente. Atribuir simultaneamente key_field = X e %data-key_field = Y na mesma linha de VALUE #( ) é ambíguo.
%param carrega os parâmetros de entrada de ações non-standard (declaradas com parameter <abstract_entity> no BDEF) e de eventos RAP. A estrutura típica é %param-<field> para o valor e %param-%control-<field> para a flag de campo provido.
%target entra na criação de composições (pai + filhos numa única request). Cada linha do tipo derivado de CREATE BY _assoc aponta para o pai via %cid_ref (ou %tky) e leva os registros-filho dentro de %target, uma tabela aninhada.
Exemplos práticos de ponta a ponta
CREATE com FIELDS ( ... ) WITH (compilador preenche o %control)
MODIFY ENTITIES OF zdemo_abap_rap_ro_m
ENTITY root
CREATE FIELDS ( key_field field1 field2 field3 field4 )
WITH VALUE #(
( %cid = 'cid_a' key_field = 7
field1 = 'ooo' field2 = 'ppp' field3 = 70 field4 = 71 ) )
MAPPED DATA(mapped)
FAILED DATA(failed)
REPORTED DATA(reported).CREATE de pai + filhos com %cid_ref e %target
MODIFY ENTITIES OF zdemo_abap_rap_ro_m
ENTITY root
CREATE FIELDS ( key_field field1 field2 field3 field4 ) WITH VALUE #(
( %cid = 'cid_root' key_field = 9
field1 = 'qqq' field2 = 'rrr' field3 = 90 field4 = 91 ) )
CREATE BY \_child FIELDS ( key_ch field_ch1 field_ch2 ) WITH VALUE #(
( %cid_ref = 'cid_root' " referência ao pai recém-criado
%target = VALUE #(
( %cid = 'cid_ch1' key_ch = 9 field_ch1 = 'aaa_ch' field_ch2 = 99 )
( %cid = 'cid_ch2' key_ch = 10 field_ch1 = 'bbb_ch' field_ch2 = 100 ) ) ) )
MAPPED FINAL(mapped)
FAILED FINAL(failed)
REPORTED FINAL(reported).CREATE seguido de UPDATE encadeado pelo %cid_ref
MODIFY ENTITIES OF zdemo_abap_rap_ro_m
ENTITY root
CREATE FIELDS ( key_field field1 field2 ) WITH VALUE #(
( %cid = 'cid5' key_field = 5 field1 = 'iii' field2 = 'jjj' ) )
UPDATE FIELDS ( field1 ) WITH VALUE #(
( %cid_ref = 'cid5' field1 = 'novo_valor' ) )
MAPPED FINAL(mapped)
FAILED FINAL(failed)
REPORTED FINAL(reported).UPDATE de instância persistida via %tky (recomendado)
MODIFY ENTITIES OF zdemo_abap_rap_ro_m
ENTITY root
UPDATE FIELDS ( field2 ) WITH VALUE #(
( %tky-key_field = 3 field2 = 'atualizado' ) )
FAILED DATA(failed)
REPORTED DATA(reported).Equivale a
%key-key_field = 3num cenário sem draft e sem late numbering — mas é portátil.
EXECUTE de action com %param e %control
MODIFY ENTITIES OF /dmo/i_travel_m
ENTITY travel
EXECUTE deductDiscount FROM VALUE #(
( %tky-travel_id = '00002772'
%param-discount_percent = 10
%param-%control-discount_percent = if_abap_behv=>mk-on ) )
RESULT DATA(result)
FAILED DATA(failed)
REPORTED DATA(reported).
COMMIT ENTITIES RESPONSE OF /dmo/i_travel_m
FAILED DATA(failed_commit)
REPORTED DATA(reported_commit).READ ENTITIES dinâmico com %control
DATA read_dyn TYPE TABLE FOR READ IMPORT zdemo_abap_rap_ro_m.
read_dyn = VALUE #(
( %key-key_field = 1
%control = VALUE #(
field1 = if_abap_behv=>mk-on
field2 = if_abap_behv=>mk-on
field3 = if_abap_behv=>mk-off
field4 = if_abap_behv=>mk-off ) ) ).
READ ENTITIES OF zdemo_abap_rap_ro_m
ENTITY root
FROM read_dyn
RESULT DATA(result)
FAILED DATA(failed).Aqui o %control decide quais campos vêm preenchidos no result — campos com mk-off voltam vazios.
MAPPED, FAILED, REPORTED e o COMMIT
Toda MODIFY ENTITIES pode retornar até três estruturas. Saber o que cada uma carrega é o que torna seu tratamento de erro confiável:
Estrutura | Conteúdo | Componentes-chave |
|---|---|---|
MAPPED | Mapeamento %cid ↔ chave final (essencial em managed/late numbering) | %cid, %key/%tky |
FAILED | Instâncias que falharam, com a causa | %cid, %cid_ref, %key/%tky, %fail-cause |
REPORTED | Mensagens (info, warning, error) | %msg, %key/%tky, %element-... |
DATA(novo_id) = mapped-root[ %cid = 'cid1' ]-%key-key_field.
LOOP AT failed-root INTO DATA(f).
IF f-%fail-cause = if_abap_behv=>cause-locked.
" trate o lock
ENDIF.
ENDLOOP.Depois da MODIFY, as mudanças vivem apenas no buffer transacional. Para persistir, use COMMIT ENTITIES:
COMMIT ENTITIES RESPONSE OF /dmo/i_travel_m
FAILED DATA(failed_commit)
REPORTED DATA(reported_commit).Em late numbering, é exatamente entre a MODIFY e o COMMIT que o método adjust_numbers transforma o %pid na chave final, dentro de %key.
%tky não é a "Semantic Key" do CDS
Essa confusão terminológica derruba muita gente. São coisas diferentes:
Conceito | O que é | Onde aparece |
|---|---|---|
%key (chave técnica RAP) | Os campos key da CDS root view — em geral a UUID. Identificador persistente da instância | BDEF Derived Types |
%tky (transactional key) | %key + %pid + %is_draft. Identificador único durante a interação transacional | BDEF Derived Types |
Semantic Key (CDS) | Annotation @ObjectModel.semanticKey em projection views — chave legível (ex.: TravelID = '00002772') para URLs amigáveis no Fiori Elements. Não é a chave primária persistida | CDS Projection View |
A semantic key melhora a UX no Fiori Elements (URLs como .../Travel('00002772') em vez de UUIDs), mas todas as operações EML continuam usando %tky/%key, baseadas na chave primária real.
Boas práticas consolidadas
Use %tky por padrão em UPDATE, DELETE, EXECUTE e referências entre entidades. Migrar para draft depois sai de graça.
Evite AUTO FILL CID quando houver encadeamento — você perde o %cid_ref nas operações seguintes da mesma request.
Sempre preencha %cid em CREATE, mesmo sem encadeamento. Use AUTO FILL CID só em batches independentes.
Prefira FIELDS ( ... ) WITH a FROM para não preencher %control na mão. Use FROM só quando precisar de controle fino por linha.
Em UPDATE, marque %control = mk-on apenas nos campos que quer alterar — evita sobrescrever dados sem querer.
Para ações com parâmetros obrigatórios, sempre sete %param-%control-<field> = mk-on.
Em atribuições entre grupos diferentes (%key → %tky, ou entre BOs distintos), use CORRESPONDING #( ... ).
Sempre cheque failed e reported antes do COMMIT ENTITIES. Em caso de erro, considere ROLLBACK ENTITIES.
Não misture %data-<campo> e <campo> na mesma linha de VALUE #( ).
%cid é volátil: vale só dentro de uma única request EML. Não tente reaproveitá-lo entre chamadas.
Perguntas frequentes
Posso usar só %key e ignorar %tky?
Em apps sem draft e sem late numbering, %key funciona porque é estruturalmente igual a %tky. Mas é uma aposta no futuro: no dia em que o BO ganhar draft, %key deixa de distinguir instância ativa de draft e seu código pode atingir o registro errado. Use %tky desde o início.
Por que meu UPDATE não altera o campo, mesmo eu tendo preenchido o valor?
Porque o %control daquele campo está em mk-off. No UPDATE, só os campos com %control = mk-on são gravados — o valor preenchido na estrutura é ignorado se a flag estiver desligada. Use FIELDS ( ... ) WITH e o compilador liga as flags por você.
Por que %cid_ref não acha o %cid que criei com AUTO FILL CID?
Porque o %cid gerado pelo runtime é opaco: você nunca conhece a string atribuída em tempo de design. Para encadear CREATE → UPDATE/DELETE ou CREATE BY _assoc, forneça o %cid manualmente em vez de usar AUTO FILL CID.
Quando o %pid vira chave de verdade?
Em late numbering, o %pid (preliminary ID) é convertido na chave final pelo método adjust_numbers, que roda entre a MODIFY ENTITIES e o COMMIT ENTITIES. Antes disso, identifique a instância pelo %tky (que carrega o %pid) ou pelo %cid.
FROM ou WITH — qual eu uso?
Regra rápida: CREATE FROM exige que você preencha %control manualmente. CREATE FIELDS ( ... ) WITH faz o compilador preencher o %control dos campos listados. E AUTO FILL CID usa WITH, nunca FROM.
Conclusão
Os componentes % deixam de ser um amontoado de prefixos estranhos quando você enxerga a lógica por trás: %tky identifica de forma portátil, %cid/%cid_ref conectam operações dentro da request, %control decide o que entra, e MAPPED/FAILED/REPORTED contam o que aconteceu. Adote %tky por padrão, preencha sempre o %cid e cheque as respostas antes do commit — o resto é consequência.
Guarde este guia como referência e volte sempre que bater a dúvida de "qual % usar aqui?".
Referências oficiais
Comentários 0
Ainda sem comentários
Seja o primeiro a comentar este artigo.
Entre na conversa
Faça login para deixar seu comentário neste artigo.