Gravação Atômica dos Dados

Boa tarde!

Vamos supor que tenho uma lista de veículos que preciso adicionar à um contrato. Estes veículos serão salvos no mesmo momento que gravar o contrato.
No Latromi é possível salvar em memória (tipo uma RX) estes veículos antes de salvar no banco de dados, pra eu não ter que criar uma tabela temporária no banco de dados só pra armazenar estes registros antes de salvar na tabela real?

Att,

Nielson santos

Olá @nielson.santos !

A abordagem de gravação atômica dos dados não é suportada pelo LATROMI pelo nível de complexidade que seria necessário para relacionar os dados “desconectados” aos dados “conectados” ao banco de dados.

Por exemplo, em uma Grid com produtos, seria muito comum que a partir do código do produto listado, fosse necessário buscar o nome do produto também.

Solução

A melhor abordagem na minha opinião é ao invés de ter uma tabela “temporária”, criar uma coluna com o Status do registro na tabela.

Por exemplo:

/* Valores da Coluna Status: 
     0 = OK 
     1 = Temporário
*/
ALTER TABLE tabela ADD status SMALLINT NOT NULL DEFAULT 0;

Usando essa abordagem, você inclui os registros com o status “Temporário”, e atualiza-os para “OK” no final do Processo.

Para limpar os registros “Temporários” que não foram confirmados (caso o usuário desista da inclusão), use o evento Closing do Formulário.

E nas demais rotinas do sistema, você pode simplesmente considerar apenas os registros com o status OK.

É então, mas o meu problema é justamente pq eu tenho o registro pai (o contrato) e os registros filhos (os veículos), vamos supor que estou criando um contrato novo, onde preciso informar 4 veículos.

Na tabela filha (veículos), eu preciso relacionar a fk da tabelade contrato com o ID do contrato, para que nessa filha saíba qual contrato pertencem os veículos, porém, eu não tenho o registro do ID do contrato ainda, pois não gravai o contrato ainda, sabe?

Ai adiciono os veículos e se for salvando na tabela com código de status 1 (por exemplo), eu tenho que fazer o mesmo na tebala de contrato, para poder adicionar a FK do contrato na tabela de veículos.

Eu queria algo mais simples, que fosse armazenando em memória esses veículos, para depois qundo gravar, eu grave uma única vez o contrato e os veículos, sem ter a necessidade de ficar alterando a tebela depois com outro código de status, entende?

Até mesmo se eu precisar alterar alguma informaçõ do veículo antes de gravar o contrato, não ter que dar um alter table toda vez mesmo antes de ter realmente gravado aquele contarto em específico.

@nielson.santos, eu entendo a sua necessidade, mas acho que na prática é mais fácil do que você imagina.

O comando ALTER TABLE que coloquei, era só um exemplo de como a coluna poderia ser criada. Este comando seria executado apenas uma vez para criar a coluna na tabela. O ideal é fazer isso fora do LATROMI inclusive.

Vou tentar criar um exemplo mais próximo da sua situação.

Tabelas

Vamos trabalhar com as tabelas as seguir.

Veiculos

Cadastro de Veículos

Coluna Comentário
:key: VeiculoId
Placa

Contratos

Cadastro de Contratos

Coluna Comentário
:key: ContratoId
Data
Status 0 = OK, 1 = Temp

ContratoVeiculos

Veículos do Contrato

Coluna Comentário
:key: ContratoId FK com Contratos
:key: VeiculoId FK com Veiculos

Objetos do Latromi

Vamos considerar que temos os seguintes objetos:

  • Formulário “Contrato” (Alteração e Inclusão de Contrato)

    Este Formulário vai estar esperando o parâmetro “ContratoId”. Quando este parâmetro for informado, vamos considerar que é umas alteração. Senão, será uma inclusão.

  • Formulário “Contrato - Veículo” (Inclusão de Veículo no Contrato)

    Este Formulário vai estar esperando o parâmetro “ContratoId”.

  • Consulta “Veículos do Contrato” (Lista os veículo de um determinado Contrato)

    Esta Consulta vai estar esperando o parâmetro “ContratoId”.

    • Botão “Incluir” chamando o Formulário “Contrato - Veículo”, e repassando o parâmetro ContratoId.
    • Link “Excluir” chamando Comando SQL para remover o veículo do Contrato.
  • Consulta “Listagem de Contratos” (Lista todos os Contratos)

    • Botão “Incluir” chamando o Formulário “Contrato”, sem passar parâmetro.
    • Link “Alterar” chamando o Formulário “Contrato”, passando o Id do Contrato da linha selecionada.

Implementação

Vou tentar explicar a implementação na ordem cronológica das operações.

Ela consiste basicamente em criar o registro da tabela principal assim que a janela for aberta. Desta forma, todos registro filhos podem ser incluídos mantendo a integridade das Foreign Keys.

Listagem de Contratos

A operação inicia quando o usuário acessa a Consulta Listagem de Contratos.

Essa Consulta vai listar apenas os registros da tabela “Contratos” que possuem Status = 0, ou seja, registro confirmados / completos.

Exemplo:

SELECT * FROM Contratos
WHERE Status = 0

Ou se preferir, você pode listar todos os Contratos, e usar uma Formatação Condicional para diferenciar os registros Completos (OK) dos Incompletos (Temp)

Inclusão de Contrato

Quando o usuário estiver na Consulta Listagem de Contratos, ele poderá incluir um novo Contrato clicando no botão “Incluir”, acessando assim o Formulário Contrato.

Neste Formulário, teremos além dos campos do Contrato, um campo do tipo “Consulta”, apontando para a Consulta Veículos do Contrato.

No evento Load do Formulário, nós podemos identificar se estamos trabalhando em uma operação de inclusão ou alteração analisando o parâmetro “ContratoId”. Se ele não foi informado, é uma inclusão, senão é uma alteração.

Usando o IF abaixo podemos identificar se é uma inclusão:

If PARAM.ContratoId = NULL Then

Dentro deste IF, vamos criar um registro “temporário” para o Contrato, e armazenar o Id em uma variável chamada “ContratoId”.

Se o valor da coluna “Contratos.ContratoId” for gerado automaticamente pelo banco (Auto Increment, Serial, Identity e etc), podemos fazer a inclusão usando uma Record dessa maneira:

INSERT INTO Contratos (Status) VALUES (1)
RETURNING ContratoId

Depois da inclusão, use o campo “ContratoId” da Record criada para alimentar a variável.

Se o parâmetro “ContratoId” foi informado, vamos apenas repassá-lo para a variável. Dessa forma, a partir deste ponto, podemos sempre usar a variável com o Id do Contrato ao invés do parâmetro.

O procedimento ficaria mais ou menos assim:

If PARAM.ContratoId = NULL Then

    // Inclui registro tempórário usando Record
   var rContratoTemp = CreateRecord("INSERT INTO Contratos .....")

   // Preenche a variável com o valor de rContratoTemp.ContratoId
   Populate( VAR.ContratoId )

Else

   // Preenche a variável com o valor do Parâmetro ContratoId
   Populate( VAR.ContratoId )

Agora que o usuário já passou pelo evento “Load”, e está com a página de inclusão de Contrato aberta, ele vai clicar no botão “Incluir” da Consulta Contrato - Veículo para inserir um veículo no contrato.

Essa consulta, dentro do Formulário, vai estar recebendo como parâmetro a variável “ContratoId”.

Contrato - Veículo

Nesta página, teremos um campo onde o usuário vai selecionar um veículo (previamente cadastrado), para então vinculá-lo ao Contrato.

Essa parte, é bem simples. Basta inserir o veículo selecionado na tabela “ContratoVeiculo” ao clicar no botão de Confirmação.

Exemplo:

INSERT INTO ContratoVeiculos ( ContratoId, VeiculoId )
VALUES ( PARAM.ContratoId, INPUT.VeiculoId )

Como o registro da tabela “Contratos” já existe, não teremos nenhum erro de vioalação de Foreign Key ao adicionar o registro.

De volta à Inclusão de Contrato

Agora que um ou mais veículos já foram vinculados ao nosso registro “Temporário”, falta apenas completar as informações do Contrato e trocar o Status para OK quando o usuário clicar no botão Salvar.

Por exemplo:

UPDATE Contratos
SET Data = '{?INPUT Data}', Status = 0
WHERE ContratoId = {?VAR ContratoId}

Mas e se o usuário clicar no botão Fechar? :thinking:

Neste caso, para reduzir a quantidade de registros “Temporários” deixados na tabela “Contratos”, podemos executar um comando SQL DELETE para excluir o registro não confirmado no evento Closing do Formulário:

DELETE FROM Contratos
WHERE ContratoId = {?VAR ContratoId}
AND status = 1

Considerações Finais

Esta é apenas uma maneira simples e funcional de fazer a inclusão de registros vinculados. Mas tudo depende da criatividade de quem estiver desenvolvendo, talvez existam outras abordagens mais interessantes do que esta (se alguém souber de outra, posta aí :wink:).

Quanto ao Status do registro, tratei apenas no registro principal (Contratos), mas se for necessário, pode ser replicados para os registros filhos.

1 curtida

Desta fora vai funcionar sim! Obrigado @daniel.giacomelli.

Mas existe a possibilidade de criação de um componente mais ou menos parecido com um ClientDataSet, TRxMemoryData ou tabela em memória pra fazer esse “trabalho”?

Infelizmente não será possível @nielson.santos.

Como eu disse, a complexidade no relacionamento dos Dados do Banco com os Dados Desconectados, adicionado a necessidade de persistir os dados entre Cliente e Servidor durante a edição tornam a implementação deste recurso inviável em aplicações Web.

Não conheço o Delphi, mas pelo que entendi, o TRxMemoryData é uma estrutura em forma de Tabela, que você alimenta, e depois consome.

Você pode criar uma estrutura como essa no banco de dados.

Por exemplo, você poderia criar um schema separado chamado “MemoryData” (ou “ViewModels”) e criar as tabelas lá com a estrutura que você usaria no TRxMemoryData. Aí quando o Formulário fosse aberto (evento Load) bastaria copiar as informações para a essas tabelas, e quando a operação fosse confirmada, faria o processo reverso.

Percerba que nos exemplos abaixo, existe uma coluna adicional "Usuário". Essa coluna é útil para evitar que a edição de um usuário interfira na edição de outro.

Exemplo:

No evento Load:

-- Limpa a tabela "virtual", caso tenha ficado alguma "sujeira"
DELETE FROM MemoryData.ContratoVeiculo 
WHERE Usuario = '{?SESSION UserName}'
AND ContratoId = '{?PARAM ContratoId}';

-- Preenche a tabela "virtual" com os dados do contrato em edição
INSERT INTO MemoryData.ContratoVeiculo (Usuario, ContratoId, VeiculoId)
   SELECT 
       '{?SESSION UserName}',
        ContratoId, 
        VeiculoId
   FROM ContratoVeiculos
   WHERE ContratoId = '{?PARAM ContratoId}'

No evento Click do botão Salvar:

-- Exclui os veículos para inserir os novos
DELETE FROM ContratoVeiculo 
WHERE ContratoId = '{?PARAM ContratoId}';

-- Copia os veículo da tabela "virtual" para a tabela definitiva.
INSERT INTO ContratoVeiculos (ContratoId, VeiculoId)
   SELECT  ContratoId, VeiculoId 
   FROM MemoryData.ContratoVeiculo
   WHERE Usuario = '{?SESSION UserName}'
   AND ContratoId = '{?PARAM ContratoId}';

E pra finalizar, no evento Closing do Formulário:

-- Limpa a tabela "virtual"
DELETE FROM MemoryData.ContratoVeiculo 
WHERE Usuario = '{?SESSION UserName}'
AND ContratoId = '{?PARAM ContratoId}';
1 curtida

Legal, bacana. Obrigado pelas dicas.

1 curtida