Anexo II
O objetivo deste material é demonstrar possíveis códigos (enxutos e seguros) para realizar
operações de acesso/manutenção em uma tabela resultante de um relacionamento NxN (muitos para
muitos) utilizando componentes DataWare (componentes que representam automaticamente informações
contidas em uma origem de dados). Nas aplicações este tipo de relação (NxN) é normalmente
denominado de relação Mestre/Detalhe. Neste tipo de relação os dados exibidos na tabela Detalhe são
dependentes dos dados exibidos na tabela Mestre. Para exemplificar essa relação vamos utilizar uma
tabela resultante da relação Receitas e Ingredientes (uma receita pode conter muitos ingredientes e um
ingrediente pode estar contido em muitas receitas). O modelo de dados utilizado neste exemplo utiliza
três tabelas relacionadas da seguinte forma:
Em uma aplicação com uma relação mestre/detalhe, os códigos discutidos no Anexo I (Cadastro
Simples) sofrerão algumas alterações (agora será necessário executar operações e controlar mais de
uma tabela). Para fazer a demonstração será utilizada uma janela de manutenção semelhante à figura
abaixo com o devido tratamento dos eventos que possam ocorrer objetivando um código simples que
possa funcionar para qualquer tela de cadastro com características semelhantes. De forma a organizar o
código, um componente ActionList será utilizado com a criação e implementação 3 novas ações
(actIncluirDetalhe, actSalvarDetalhe, actExcluirDetalhe) além daquelas sete já discutidas anteriormente
actNovo, actLocalizar, actSalvar, actCancelar, actExcluir, actSair, actLocalizarPorDescricao).
Edit
A figura a seguir ilustra as alterações necessárias para ativar uma relação mestre/detalhe entre dois
componentes SQLQuery:
Com essa relação configurada, quando a tabela MESTRE está ativa a tabela DETALHE se
comporta exibindo somente os registros que fazem referência ao registro atual da tabela MESTRE. Em
outras palavras, o comando SQL da SQLQueryDetalhe é alterado de forma a substituir o valor do
parâmetro “:ID_RECEITA” pelo valor contido no campo de mesmo nome (ID_RECEITA) da
SQLQueryMestre.
procedure AbrirConsultas();
begin //Se a transação não está ativa então inicia ela
if not SQLTransaction.Active then SQLTransaction.StartTransaction;
... //Restante do código discutido no Anexo I
//Se a tabela detalhe e suas tabelas relacionadas não estão ativas então devem ser ativadas
if not TabelaRelacionadaComDetalhe.Active then TabelaRelacionadaComDetalhe.Open;
if not TabelaDetalhe.Active then TabelaDetalhe.Open;
procedure FecharConsultas();
begin //se as tabelas relacionadas estão ativas então as mesmas devem ser fechadas
... //Restante do código discutido no Anexo I
//Se a tabela detalhe e suas tabelas relacionadas estão ativas então devem ser fechadas
if TabelaDetalhe.Active then TabelaDetalhe.Close;
if TabelaRelacionadaComDetalhe.Active then TabelaRelacionadaComDetalhe.Close;
O código a seguir demonstra como sincronizar o estado da tabela mestre e da tabela detalhe.
Sempre que a tabela mestre estiver ativa a tabela detalhe deve ser ativada e sempre que a tabela mestre
estiver inativa então a tabela detalhe deve ser inativada. Essa alteração será feita no procedimento
HabilitarDesabilitarControles que também será alterado para habilitar e/ou desabilitar o
GroupBox onde estão os controles da tabela detalhe.
procedure HabilitarDesabilitarControles;
begin
... //Código que habilita e desabilita os controles visuais
//de acordo com o status da tabela mestre
//Se a tabela detalhe está ativa e não contém nenhum registro (está vazia) então
//a ação de gravação da tabela detalhe deverá ficar desabilitada
if TabelaDetalhe.Active and TabelaDetalhe.IsEmpty then
actSalvarDetalhe.Enabled := false;
end;
A inclusão de um registro na tabela detalhe ocorre normalmente através do método insert e deve
atribuir ao campo que representa a relação entre as tabelas o valor da tabela mestre. Também é valido
verificar se o status da tabela detalhe já não é de inclusão, neste o método insert nem precisa ser
executado. Se existirem campos que precisarem ser inicializados ou então componentes que precisam
receber o foco este é o momento apropriado para isso.
//Atribui ao campo que faz a relação entre a tabela mestre e a tabela detalhe
//o valor do campo chave da tabela mestre
TabelaDetalhe.FieldByName('CampoRelacaoMestreDetalhe').Value :=
TabelaMestre.FieldByName('CampoChave').value;
//Aqui pode-se indicar componentes que receberão o foco e/ou componentes que
//precisarão ser inicializados.
DBEditDetalheQualquer.SetFocus;
DBLkpCmbBxDetalheQualquer.ItemIndex := -1
//Após o usuário clicar no botão que faz a inserção na tabela detalhe então
//a ação de salvamento deve ficar habilitada
actSalvarDetalhe.Enabled:=true;
end;
A gravação das informações da tabela detalhe vai exigir um pouco de atenção. O procedimento de
gravação/persistência é simples: o comando Post grava os dados no DataSet, o comando
ApplyUpdates envia os dados do DataSet para serem persistidos na base de dados e por último, a
ação de Commit da transação vai efetivar os dados e encerrar a transação. Essa última situação é
justamente onde se deve prestar mais atenção: quando a transação for encerrada, todos os DataSets
ligados a ela serão automaticamente fechados. Isso significa, transpondo a situação para o exemplo dos
ingredientes e receitas, que a cada vez que um ingrediente for incluído em uma receita o usuário teria
que novamente localizar a receita para fazer uma nova inclusão ou alteração de dados. Para contornar
essa situação, a cada vez que os dados da tabela detalhe forem persistidos haverá necessidade de ativar
novamente as consultas e posicionar os registros. O código a seguir demonstra como isso pode ser feito:
var //variáveis auxiliares para guardar informações do registro que será salvo
Chave1, Chave2 : integer; //Chave1 = Chave da tabela mestre, e parte da chave
begin // da tabela detalhe, Chave2 = chave complementar da tabela detalhe
A exclusão de uma informação na tabela detalhe pressupõe que o usuário esteja posicionado sobre
o registro ao qual deseja excluir. Assim como na ação de gravação, a ação de exclusão exigirá que a
transação seja encerrada, de forma que será necessário prever uma forma de reposicionar o registro da
tabela mestre
var chaveTbMestre: integer; //Variável auxiliar para guardar a chave da tabela mestre
begin
//Se a tabela detalhe está ativa e não estiver vazia (há algo para excluir)
if (TabelaDetalhe.Active) and (not TabelaDetalhe.IsEmpty) then
begin
//Guarda em uma variável auxiliar o campo da tabela detalhe que faz relação com
//a tabela mestre
chaveTbMestre := TabelaDetalhe.FieldByName('CampoRelacaoMestreDetalhe').asInteger;