Fórum duvida sobre TRANSACAO #394332
25/01/2011
Caros amigos minha duvida e referente a seguinte situacao:
ja possuo gravado o meu produto, so que este produto e composto por varios
itens, e para agregar os itens que compoe cada produto o usuario deve
selecionar o produto e adicionar os itens(um ou mais) para este produto;
exemplo:
produto: "JOGO DE JANTAR"
ITENS DO PRODUTO: "PRATOS"
"TALHERES"
"COPOS"
A minha preocupacao e que ao salvar a operacao de composicao dos itens ao
produto, sejam gravados apenas alguns dos itens informado pelo usuario
devido a alguma falha na conexao com o bd ou pau no windows.Para solucionar
este problema fiz uma pesquisa onde a melhor situacao seria trabalhar com
transacoes que garante em 100% a eficacia da operacao junto ao bd; porem ao
aplicar o exemplo de transacao para o caso exemplificado acima obtive o seguinte codigo:
procedure TF_PRODUTOS.BGRAVARClick(Sender: TObject);
VAR T:TTransactionDesc;
BEGIN
DM.CDS_DETALHE.Open;
cds_temp.OPEN;
TRY
while cds_temp.Eof = false do
begin
T.IsolationLevel := xilREADCOMMITTED;
DM.CONECT_PLACAS_2011.StartTransaction(T);
dm.CDS_DETALHE.Append;
dm.CDS_DETALHEID_PRODUTO.Value:=CDS_TEMPCOD_PROD.Value;
dm.CDS_DETALHEID_PECA.Value:=CDS_TEMPCOD_PECA.Value;
dm.CDS_DETALHE.Post;
DM.CDS_DETALHE.ApplyUpdates(0);
DM.CONECT_PLACAS_2011.Commit(T);
CDS_TEMP.Next;
end;
Except
DM.CONECT_PLACAS_2011.Rollback(T);
SHOWMESSAGE('ERRO AO GRAVAR NOVO REGISTRO');
END;
END;
No codigo acima tenho os itens informados pelo o usuario armazenadas em um
clientDataSet (cds_temp), onde faco um loop para varrer esta tabela temporaria
para obter todos os itens informados pelo usuario e armazena-las na tabela no bd atraves
de transacao.
e ficou a seguinte duvida:
no codigo acima para cada iten adicionado tenho que abrir e comitar a transacao, e caso haja
algum erro nesta transacao e dado um rollback voltando os dados em seu estado inicial nao
gravando nenhuma alteracao no bd.
Mas isto obviamente na minha visao ira dar errado, pois se tenho mais de um iten para
cada produto e o erro acontece na insercao do segundo registro o rollback acontecera
somente para a insercao onde o erro ocorreu ou seja o segundo registro, pois o primeiro iten
ja foi gravado pela transacao nao adiantando nada realizar a operacao dentro de uma transacao
para garantir a composicao exata do produto informada pelo usuario ela sera gravada parcialmente.
E quando tento executar o codigo abaixo onde gravo todos os registros antes de dar o commit na transacao
e gerado um erro ao tentar inserir o segundo registro "key violation".(em virtude de o campo dm.CDS_DETALHEID
ser do tipo autoincremento com triggers e genarator firebird )ja foi informado no clientdataset que
fild nao e requirido.
procedure TF_PRODUTOS.BGRAVARClick(Sender: TObject);
VAR T:TTransactionDesc;
BEGIN
DM.CDS_DETALHE.Open;
cds_temp.OPEN;
TRY
while cds_temp.Eof = false do
begin
T.IsolationLevel := xilREADCOMMITTED;
DM.CONECT_PLACAS_2011.StartTransaction(T);
dm.CDS_DETALHE.Append;
dm.CDS_DETALHEID_PRODUTO.Value:=CDS_TEMPCOD_PROD.Value;
dm.CDS_DETALHEID_PECA.Value:=CDS_TEMPCOD_PECA.Value;
dm.CDS_DETALHE.Post;
DM.CDS_DETALHE.ApplyUpdates(0);
CDS_TEMP.Next;
end;
DM.CONECT_PLACAS_2011.Commit(T);
Except
DM.CONECT_PLACAS_2011.Rollback(T);
SHOWMESSAGE('ERRO AO GRAVAR NOVO REGISTRO');
END;
END;
em fim a pergunta:
Como posso gravar mais de um registro dentro de uma mesma transacao com campo chave do tipo autoincremento?
Posts
25/01/2011
Silvio Caetano
Cara acho que no seu caso você não necessita de Transactions.
Você pode fazer assim.
procedure TF_PRODUTOS.BGRAVARClick(Sender: TObject);
BEGIN
DM.CDS_DETALHE.Open;
cds_temp.OPEN;
TRY
while cds_temp.Eof = false do
begin
dm.CDS_DETALHE.Append;
dm.CDS_DETALHEID_PRODUTO.Value:=CDS_TEMPCOD_PROD.Value;
dm.CDS_DETALHEID_PECA.Value:=CDS_TEMPCOD_PECA.Value;
dm.CDS_DETALHE.Post;
CDS_TEMP.Next;
end;
DM.CDS_DETALHE.ApplyUpdates(0); //Faz o mesmo que o Commit
Except
DM.CDS_DETALHE.CancelUpdates; // Faz o mesmo que o Rollback
SHOWMESSAGE('ERRO AO GRAVAR NOVO REGISTRO');
END;
END;
Qual a base de dados que você está utilizando?
Caso esteja utilizando o firebird, utilize o CommitRetaining, a diferença do Commit para o CommitRetaining é que o Commit Fecha a tabela e o CommitRetaining não fecha a tabela, porém o CommitRetaining não permite
o Garbage Collection o que pode fazer seu banco de Dados Inchar (Nada impede de fazer essa manutenção na base manualmente). Veja qual atende sua necessidade e passe a utilizar o "commit" correto para cada situação!!
Qualquer coisa estou à disposição!
Atenciosamente,
Emanoel Deivison
Agradeco aos amigo a resposta, utilizo sim o FIREBIRD, o CommitRetaining pode ser utilizado nao aparece no codigo automatico do delphi e necessario declarar alguma biblioteca especifica ou ele so pode ser utilizado direto no banco?
Qual a base de dados que você está utilizando?
Caso esteja utilizando o firebird, utilize o CommitRetaining, a diferença do Commit para o CommitRetaining é que o Commit Fecha a tabela e o CommitRetaining não fecha a tabela, porém o CommitRetaining não permite
o Garbage Collection o que pode fazer seu banco de Dados Inchar (Nada impede de fazer essa manutenção na base manualmente). Veja qual atende sua necessidade e passe a utilizar o "commit" correto para cada situação!!
Qualquer coisa estou à disposição!
Atenciosamente,
Emanoel Deivison
Caro Emanoel agradeco sua ajuda e acho que vc tem razao porem ainda persiste um erro na rotina que vc apresentou que e:
na hora de gravar (post), pois o campo chave da tabela DETALHES (DETALHE_ID) e do tipo auto incremento, quando vou salvar o segundo registro da um erro de chave pois a chave so e gerada na hora que os dados sao realmente persistidos no banco mesmo marcando como campo nao requirido no clientdataset. Sera impossivel trabalhar com campo autoincremento nesta situacao?
25/01/2011
Abdenago Alvim
Prezado Miguel, No exemplo citado, você possui uma única transação que deverá "funcionar" por completo ou não, que é cadastrar o produto "JOGO DE JANTAR" e os itens "PRATOS", "TALHERES" e "COPOS".
Portanto você deverá startar uma única transação para esse processo e depois de completada confirmar a transação "commit" ou cancelar "rollback" caso ocorra algum problema.
Então você deverá iniciar a transação "StartTransaction" antes do while que, pelo que entendi no seu exemplo, irá armazenar os itens do produto. Há, o "commit" também deverá ficar forá do while...
Você pode efetuar as devidas atribuições do campo que está apresentando problemas no seu evento: "BeforeInsert" (ou algo similar) no seu engine de acesso a dados (infelizmente não tenho como fazer um exemplo prático devido a nem ter o delphi instalado em minha máquina, postei aqui (e sempre postarei) devido a ser situações que já passei e ter sido essa a forma que achei para solução!!
Qualquer coisa estou à disposição!!
PS. Se não solucionar e for preciso instalar o delphi em minha máquina, sem problemas (até pq não uso na empresa que trabalho mais não deixo de ser apaixonado pelo Delphi, tendo todas as versões desde a 5.0 até o Prism XE).
Abração e boa sorte ai para vc e todos os amigos delphianos de coração como eu!!
Emanoel Deivison
Recife - PE
Miguel , Miguel ....Muita coisa para ler e as vezes os pequenos detalhes fazem a diferença
Sua dúvida é uma dúvida muito persitente , mas ela envolve alguns conceitos que
para responder adequadamente dependerá de como vc montou este Relacionamento
Existe no RAD do Delphi algo que se chama Nested Datasets ... Eu não sei se vc esta usando esta estrutura
(A grosso modo é um DataSetProvider controlando os dois ClientDataSet)
Se Vc tiver usando esta Arquitetura é so dar Um ApplayUpdates No Mestre que todas as Alteraçoes
do Escravo serão comitadas . Não precisa ficar se procupando com Transaçoes pq a propria estrutura
do midas cuida disso para Vc .
Usar Transaçoes geralmente qnd se aplica braçalmente as operaçoes de insert Delite Updade
no modo Rad que supostamente vc esta fazendo é so Utilizar o Nested Datasets
ja que a principio tudo levar a crer que estamos diante de um esta Relacionamento
caracterizando Um Mestre e um Escravo
Mas e o problema do campo Violado ..??????
Primeiro passo vc ja fez , foi coloca-lo com a propriedade Requerido igual a False
porém isto so não resolve ,alem disso vc tem que atribuir um valor válido para ele ,
Pq na hora de comitar (ApplayUpdates) este valor não pode estar duplicado na sua base de dados
tem que ser um valor válido
e como gerar este Valor válido ???
[E praxi resolver esta questão , utilizando generator e trigger na base de dados e uma QUERY separada que ira
a cada append no CdsDetalhe , ela sera executada para pegar um VALOR Válido que sera atribuido
ao referido campo
Veja mais ou menos o codigo
Function TDm.RetornaChaveFirebird(Indice: String): Integer;
var
{Declarando a query que sera utilizada para pegar o valor gerado.}
QryId: TSQLQuery;
begin
{Instanciado o objeto.}
QryId := TSQLQuery.Create(nil);
try
{Preparando a query para a execução}
QryId.SQLConnection := Conexao;
{SQL que irá pegar o registro gerado, dado onome do generator da tabela.}
QryId.SQL.Add('SELECT GEN_ID(' + Indice +',1) AS ID_RET FROM RDB$DATABASE');
QryId.Open;
if QryId.IsEmpty then
{Não ocorreu geração, por isso o cadastro não pode continuar.}
raise Exception.Create('Não foi criado nenhum valor.')
else
{Retornando o valor gerado.}
result := QryId.FieldByName('ID_RET').AsInteger;
finally
{Apos o uso da query, liberando a memoria.}
QryId.Free;
end;
end;
Ja no DataSet uso assim.
procedure TSeuForm.SeuClientDataSetDetalheBeforePost(DataSet: TDataSet);
begin
try
DataSet.FieldByName('SEUCAMPOAUTOINCREMNTO').AsInteger := Dm.RetornaChaveFirebird('gen_produto_id');
excpet
begin
//enviar mensagem ???
abort;
end;
end;
end;
A peultima configuração que vc deve fazer se diz respeito ao ProviderFlags dos SQL
Geralmente vc configura deixando os campos Chave Primaria com a seguinte propriedade
[pfInUpdate,pfInWhere,pfInKey]
e pars os demais campos somente
[pfInUpdate]
eu Disse Componentes SQL mas essas configuraçoes devem ser passadas para os Respectivos
ClientDataSet associados... Ha forma automaticas de propagar essas configuraçoes
mas tb pode ser a da forma Manual . Ito é repedindo o valor para os field do CDS iguais
aos definidos na Query
por fim Coloque a Propriedade UpdateMode do DataSetProvider para upWhereKeyOnly
Essas configuraçoes tb devem ser feitas caso vc queira utilizar o esquema de
Transação ... Lembrando que no delphi 2010 ha novas classes que controlam a Transação
O mais importante é o que vc desconfiou e um amigo ai em cima retrucou que vc coloque
o comit fora do Laço while
por fim não esqueça , se tiver usando o Nested Datasets tanto no esquema RAD do Delphi
Qnt na Utilização da Transação sera necessário so dar um ApplayUpdates porque quem trafegara
o Delta sera o DataSetProvider que controla tanto o Mestre qunt no escravo
Como são muitas informaçoes , leia todas com atenção , pq qualquer descuido pode ser motivo
para ficar mais tempo diante do Pc
CAROS AMIGOS MUITO OBRIGADO PELAS RESPOSTAS QUE ME AJUDARAM A CHEGAR A CONCLUSAO DE QUE O MELHOR ESQUEMA A SER APLICADO NO MEU CASO E DO NOSSO AMIGO SILVIO ANTONIO CAETANO, QUE AINDA ESTAVA APRESENTADO ERRO NA HORA DE SALVAR OS REGISTROS DEVIDO AO FATO DE TER UM CAMPO CHAVE PRIMARIA COM AUTO INCREMENTO NA TABELA DETALHES , POREM RESOLVI OMITINDO ESTE CAMPO NA INSTRUCAO SQL NO COMANDTEXT DO SQLDATASET E CONSEQUENTEMENTE DO CLIENTDATASET, POIS TRATA-SE APENAS DE UMA INCLUSAO NO BD ONDE NAO PRECISO TER O VALOR DE CHAVE DA TABELA DETALHES SO PRECISO DO VALOR DA CHAVE DA TABELA PRODUTOS E DA TABELA PECAS QUE JA E CADASTRADO PREVIAMENTE EM UMA TELA DE CADASTRO DE PRODUTOS E CADASTRO DE PECAS.
RESUMINDO A ESTRUTURA DE BD QUE TENHO.
*TABELA DE PRODUTOS (ID_PRODUTO,DESCRICAO_PROD)
*TABELA DE PECAS (ESTAS IRAO COMPOR OS PRODUTOS) (ID_PECA,DESCRICAO_PECA)
*TABELA DETALHES (ONDE GUARDO A INFORMACAO DE QUAL PECA PERTENCE A QUAL PRODUTO, ID_DETALHES, ID_PRODUTO, ID_PECA)
RESUMINDO A ESTRUTURA DE FORMULARIOS DE CADASTROS QUE TENHO.
*PRIMEIRO CRIO OS PRODUTOS, NA TELA DE CADASTRO DE PRODUTOS
*SEGUNDO CRIO AS PECAS QUE PODERAO COMPOR UM OU VARIOS PRODUTOS, NA TELA DE CADASTRO DE PECAS
*TERCEIRO CRIO A ASSOCIACAO DOS PRODUTOS COM AS PECAS DESEJADAS, NA TELA DE COMPOSICAO DE PRODUTOS.
ACREDITO QUE ISTO SEJA UMA RELACAO (MUITOS PARA MUITOS), POR ISSO CRIEI A TERCEIRA TABELA, ACREDITO SER A FORMA CORRETA DE ARMAZENAR ESTAS INFORMACOES.
Miguel , se vc não precisa ter o Valor de Chave da Tabela detalhes não precisa fazer de fato aquele esquema que lhe passei . Vc podia ter dito isto antes ...
como foi dito o ApplayUpdates tem este controle transactional , o que dispensa o uso de transaçoes explicitas
Porém mais uma vez eu reitero , que num ambiente RAD do Delphi é altamente recomendado que se utilize
o Nest DataSet para fazer esta Arquitetura Mestre-Escravo . O ApplayUpdates deve ser dado no Mestre e não
no Detalhe . Bem esta é a minha opinião