sexta-feira, 12 de dezembro de 2008

Trabalhando com tabela Mestre e Detalhe no Delphi

E aew pessoal.
Esse post foi feito pelo meu amigo Marcos Carmo. Ele esta contribuindo com esse artigo falando sobre Mestre Detalhe no Delphi utilizando componentes ADO, DataSetProvider e ClientDataSet.
Ele também disponibilizou seu e-mail para quem quizer tirar dúvidas, enviar críticas ou sugestões com relação ao artigo:
E-mail: m4rk0s@gmail.com
MSN: m4rk0s_c4rm0@hotmail.com


SITUAÇÃO
Iniciei este artigo pensando na dificuldade que inúmeros programadores têm quando trabalham com tabelas Mestre>Detalhe, inclusive eu tive essa dificuldade quando comecei a programar.
Conheci várias maneiras para resolver isso, sendo que julguei apenas 2 delas como viáveis e irei explicar o método que prefiro e utilizo.

Métodos
Aqui estão alguns métodos que encontrei com suas respectivas qualidades e defeitos.

Método 1
Iniciar transação ao abrir o form, gravar a tabela mestre, dessa maneira você obtém o ID_MESTRE e pode gravar direto na Detalhe, em caso de cancelamento basta dar um Rollback.
Qualidade: Fácil e rápido de programar
Defeito: Transação muito tempo ativa, praticamente deixa o sistema monousuário.

Método 2
Inserir o Mestre ao abrir o form e entrar em modo de edição, dessa maneira você obtém o ID_MESTRE e pode gravar direto na Detalhe, em caso de cancelamento deverá deletar os registros.
Qualidade: Fácil e rápido de programar
Defeito: É necessário apagar tudo o que foi incluido em caso de cancelamento e cada item incluido é um acesso ao banco de dados.

Método 3
Inserir os dados em um StringGrid e passar gravar tudo ao confirmar.
Qualidade: Acesso único ao banco de dados.
Defeito: Trabalhar com StringGrid é trabalhoso, exige muitas linhas de código e tempo.

Método 4
Inserir o Mestre ao criar o form e entrar em modo de edição, sendo a query detalhe utilizando o LockType como ltBatchOptimistic, dessa maneira você obtém o ID_MESTRE e pode gravar direto na Detalhe. ltBatchOptimistic serve para trabalhar em cache, caso haja cancelamento deverá deletar o registro Mestre.
Qualidade: Considero esse método viável, é rápido de programar e trabalha em cache, tendo ótimo desempenho e contato único com o banco de dados.
Defeito: É necessário apagar o registro em caso de cancelamento e se o ID_MESTRE for um campo identidade vc perde um número da sequência.

Método 5 (Vencedor)
Utilizar ClientDataSet para a tabela Detalhe, trabalhando tudo em cache e acessando o banco apenas quando a operação for finalizada.
Qualidade: Faz tudo em cache tendo ótimo desempenho e gerando o ID_MESTRE apenas na finalização da operação.
Defeito: Utiliza muitos componentes (query, datasetprovider, clientdataset) e precisa fazer looping na tabela detalhe para gravar o ID_MESTRE.


O QUE É PRECISO

1 TADOQuery (qryMestre) - para a tabela Mestre (SELECT * FROM MESTRE)
1 TADOQuery (qryDetalhe)- para a tabela Detalhe (SELECT * FROM DETALHE WHERE ID_MESTRE = :ID_MESTRE)
1 TDataSetProvider (dspDetalhe) - que será ligado a TADOQuery da tabela detalhe {dspDetalhe.DataSet := qryDetalhe}
1 TClientDataSet (cdsDetalhe) - que será ligado ao TDataSetProvider {cdsDetalhe.ProviderName := dspDetalhe.Name}
e o básico: TForm, TButton, TDBEdit, TDBLookupComboBox, TDBGrid, etc...

É importante ressaltar que para trabalhar com o TClientDataSet é necessário ter a midas.dll na pasta system32 ou na pasta do executável do software. Existem algumas pessoas que adicionam a midas.dll ao projeto, o problema de fazer isso é que o projeto fica imenso.


COMO FAZER (HOW TO)

Não vou entrar em detalhes quanto ao básico considerando que este tutorial é para quem tem um nível intermediário com Delphi.

Primeiros passos:
Antes de mais nada vá ao dspDetalhe e altere a propriedade UpdateMode para UpWhereKeyOnly, isso significa que quando o TClientDataSet montar a instrução UPDATE (em uma edição) a única coisa que entrará na cláusula WHERE será a chave da tabela (isso depende de umas configurações que faremos adiante com os fields).
Agora vamos aos fields.
Adicione-os nas Querys e no ClientDataSet (considerando as ligações que foram citadas acima).

Agora as propriedades dos fields da tabela Detalhe. A qryDetalhe e o cdsDetalhe deverão estar iguais. ProviderFlags, todos os fields do tipo fkData (exceto o ID_DETALHE se ele for Identity) deverão estar pfInUpdate = True, o ID_DETALHE deve estar com pfInWhere e pfInKey como true. Se o ID_DETALHE for Identity o delphi o trouxe como ReadOnly True, é importante que ele seja alterado para false para um procedimento que será explicado adiante. Fields do tipo Lookup, Calculated, InternalCalc ou Aggregate devem estar com todas as ProviderFlags como False.
Importante: Fields do tipo Lookup devem estar com a propriedade LookupCache = True, caso contrário você terá a mensagem 'Erro desconhecido.' e vai surtar como eu surtei um dia.

Uma informação importante, todos os trabalhos da tabela detalhe serão feitos no cdsDetalhe, então fields lookup por exemplo não precisam ser adicionados a qryDetalhe.
Clique com o botão direito no cdsDetalhe e clique na opção FetchParams (para ele pegar o parâmetro ID_MESTRE da qryDetalhe conforme exemplo acima).

Agora vamos às linhas de código!
- qryMestre.Append (ela deve estar aberta: qryMestre.Open)
Inserção:
- O parâmetro da cdsDetalhe deve estar com um ID que não exista na tabela Mestre, 0 por exemplo, se o campo for Identity ele nunca terá o valor 0. (cdsDetalhe.Params.ParamByName('ID_MESTRE').Value := 0)
Edição:
- O parâmetro da cdsDetalhe deve estar o valor do ID da tabela Mestre. (cdsDetalhe.Params.ParamByName('ID_MESTRE').Value := qryMasterID_MESTRE.Value)
Abra a cdsDetalhe agora que ela já tem o parâmetro (cdsDetalhe.Open)
Declare no seu form uma variável pública inteira inicializada com valor -1 (no meu exemplo será ID_DETALHE). A variável deve ser negativa e será decrementada para que não haja violação de chave.
o evento OnNewRecord do cdsDetalhe você deverá fazer o seguinte:
cdsDetalheID_DETALHE.Value := ID_DETALHE; //por isso a propriedade ReadOnly deve estar como false
Dec(ID_DETALHE);
- Agora trabalhe nos botões referente à tabela Detalhe:
Inserção: cdsDetalhe.Append;
Edição: cdsDetalhe.Edit;
Confirmação: cdsDetalhe.Post;
Cancelamento: cdsDetalhe.Cancel;
Exclusão: cdsDetalhe.Delete;
- No botão de finalização do processo (confirmando) deve estar um código semelhante a este:
try (opcional)
//Iniciar transação (opcional)
qryMestre.Post;
cdsDetalhe.DisableControls; //Apenas para aumentar a performance do looping que será feito.
cdsDetalhe.First;
while not cdsDetalhe.Eof do
begin
cdsDetalhe.Edit;
cdsDetalheID_MESTRE.Value := qryMestreID_MESTRE.Value;
cdsDetalhe.Post;
cdsDetalhe.Next;
end;
cdsDetalhe.EnableControls;
if cdsDetalhe.ApplyUpdates(0) <> 0 then //Caso haja um erro ao incluir os itens ele gera uma exceção > 0 não applica atualizações com erros > -1 ignora erros e qualquer valor maior que 0 é a quantidade de erros que ele irá permitir
Raise Exception.Create('Erro ao incluir os itens (cdsDetalhe.ApplyUpdates(0))')
//commit (opcional)
except (opcional)
on e:Exception do
begin
//Retorna transação (rollback)
Application.MessageBox(pChar('Erro: '+e.Message),'Atenção',MB_ICONERROR);
end;


Bom.. é isso aí...
Não sou muito bom para explicar, mas acho que da pra entender.
Qualquer dúvida basta me contatar por e-mail.

3 comentários:

Unknown disse...

Poxa, esse cara q fez esse texto é foda.. muito gatão, eu ficaria com ele se ele não fosse eu xD
uahsuahsuhau..

Abraços Maumau.

Unknown disse...
Este comentário foi removido pelo autor.
Carlos disse...

até q enfim, um comentário sobre cds q presta na internet