Blog SAPienteSAPiente
RAP

Como usar os BDEF Derived Types no SAP RAP: %tky, %cid e %control na prática

SAPiente8 de jun. de 2026· 4 min read

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 em UPDATE, DELETE e EXECUTE — ela já carrega %is_draft e %pid quando existem.

  • %cid identifica uma instância recém-criada dentro da request; %cid_ref aponta de volta para ela em operações encadeadas.

  • AUTO FILL CID gera o %cid automaticamente — mas aí você perde a capacidade de encadear com %cid_ref.

  • %control decide, 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:

  1. Prepara o código para uma futura migração para draft sem retrabalho.

  2. %key sozinha não distingue uma instância ativa de uma instância draft com a mesma chave. Num app com draft, usar %key pode atingir o registro errado.

  3. Em respostas FAILED/REPORTED, atribuições diretas wa-%tky = ...-%tky sã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 %data

Regra 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

  • CREATE standalone, sem operações encadeadas na mesma MODIFY.

  • ✅ Criação em lote de instâncias independentes, onde só importa o mapeamento final via MAPPED.

  • ✅ Cenários draft com CREATE puro, onde a continuidade transacional é cuidada pelo framework de draft.

  • ❌ Evite quando há encadeamento CREATEUPDATE/DELETE/EXECUTE na mesma request.

  • ❌ Evite quando há CREATE BY _assoc referenciando o pai recém-criado via %cid_ref — nesse caso, forneça o %cid manualmente.

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 %control automaticamente para os campos listados.

  • Com FROM ... (sem FIELDS), você tem que preencher %control explicitamente.

  • Em UPDATE, só os campos com %control = mk-on são atualizados. Campos com mk-off mantê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 → %key

Garanta 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 = 3 num 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

  1. Use %tky por padrão em UPDATE, DELETE, EXECUTE e referências entre entidades. Migrar para draft depois sai de graça.

  2. Evite AUTO FILL CID quando houver encadeamento — você perde o %cid_ref nas operações seguintes da mesma request.

  3. Sempre preencha %cid em CREATE, mesmo sem encadeamento. Use AUTO FILL CID só em batches independentes.

  4. Prefira FIELDS ( ... ) WITH a FROM para não preencher %control na mão. Use FROM só quando precisar de controle fino por linha.

  5. Em UPDATE, marque %control = mk-on apenas nos campos que quer alterar — evita sobrescrever dados sem querer.

  6. Para ações com parâmetros obrigatórios, sempre sete %param-%control-<field> = mk-on.

  7. Em atribuições entre grupos diferentes (%key → %tky, ou entre BOs distintos), use CORRESPONDING #( ... ).

  8. Sempre cheque failed e reported antes do COMMIT ENTITIES. Em caso de erro, considere ROLLBACK ENTITIES.

  9. Não misture %data-<campo> e <campo> na mesma linha de VALUE #( ).

  10. %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 CREATEUPDATE/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

TagsEMLDesenvolvedorClean Core
Avalie este conteúdo
para avaliar
SAPiente
@sapiente

Blog sobre SAP, ABAP, BTP, Fiori e tudo que envolve o ecossistema SAP.

Ver perfil →
FacebookInstagramYouTubeTwitter

Comentários 0

Entre na conversa

Faça login para deixar seu comentário neste artigo.

Ainda sem comentários

Seja o primeiro a comentar este artigo.