De acordo com
a solicitação de alguns sócios neste artigo estarei montando um exemplo bem
simples utilizando o Delphi 7 junto com o Firebird 1.54 (Banco de dados
utilizado pela grande quantidade de nossos sócios). Temos duas matérias que
tratam de relacionamentos Mestre/Detalhe, mas nesta matéria procurarei
relembrar algumas informações importantes que foram já citadas nestes artigos e
montar um cadastro do tipo Pai/Filho/Neto a fim de ajudar uma quantidade
expressiva de sócios nas dúvidas de configurações dos componentes e criação do
banco de dados.
Uma dúvida
bastante solicitada em nosso suporte seria qual DLL utilizar para o Banco
Firebird? O Firebird utiliza a DLL GDS32 ou a FBCLIENT, mas qual a diferença
entre elas? Simples, a GDS32 é para o banco Interbase, mas funciona para o
Firebird e a FBCLIENT é do próprio Firebird. Por isto é altamente recomendado
utilizar a FBCLIENT em se tratando do Firebird.
Finalmente
iremos verificar a estrutura de nossas tabelas criadas no nosso banco de dados.
Observe abaixo:
CREATE TABLE TBPAI (
COD_PAI
INTEGER NOT NULL,
NOME_PAI
VARCHAR(35));
ALTER
TABLE TBPAI ADD CONSTRAINT PK_TBPAI PRIMARY KEY (COD_PAI);
Após criada a tabela Pai verifique veja como fica nossa tabela
populada:
Figura 01. Tabela Pai
CREATE
TABLE TBFILHO(
COD_FILHO
INTEGER NOT NULL,
NOME_FILHO VARCHAR(40),
COD_PAI INTEGER);
ALTER TABLE TBFILHO ADD CONSTRAINT PK_TBFILHO PRIMARY
KEY (COD_FILHO);
ALTER TABLE TBFILHO ADD CONSTRAINT FK_TBFILHO_1
FOREIGN KEY (COD_PAI)
REFERENCES TBPAI (COD_PAI) ON DELETE CASCADE ON UPDATE
CASCADE;
Após criada a tabela Filho verifique veja como fica nossa
tabela populada:
Figura 02. Tabela Filho
Na tabela
tbfilho foi criado duas Constraints, uma para definir a chave primária e outra
para referenciar com a chave primária da tabela pai.
CREATE TABLE TBNETO (
COD_NETO
INTEGER NOT NULL,
NOME_NETO VARCHAR(35),
COD_FILHO
INTEGER);
ALTER TABLE TBNETO ADD CONSTRAINT PK_TBNETO PRIMARY
KEY (COD_NETO);
ALTER TABLE TBNETO ADD CONSTRAINT FK_TBNETO_3 FOREIGN
KEY (COD_FILHO) REFERENCES TBFILHO (COD_FILHO) ON DELETE CASCADE ON UPDATE
CASCADE;
Após criada a tabela Neto verifique veja como fica nossa
tabela populada:
Figura 03. Tabela Neto.
Na tabela
tbneto foi criado três constraints, uma para definir a chave primária, outra
para referenciar com a chave primária da tabela tbpai e a útilma para
referenciar com a chave primária da tabela tbfilho.
Relembrando: Dica
Importante: A Definição dos campos idênticos nas três tabelas foi feito para
que possamos utilizar o recurso do DBExpress ao qual no momento da inclusão do
registro Filho e Neto o código da tabela Pai e da Filho será enviado
automaticamente para o campo das tabelas Filho e Neto.
Iremos
criar três generators, um para cada campo (PK) das tabelas. Observe abaixo:
CREATE
GENERATOR GENPAI;
SET GENERATOR GENPAI TO 0;
CREATE GENERATOR GENFILHO;
SET GENERATOR GENFILHO TO 0;
CREATE GENERATOR GENNETO;
SET GENERATOR GENNETO TO 0;
Iremos incrementar o
generator através de nossa aplicação.
Criando um projeto de
exemplo
Iremos
neste momento criar um projeto de exemplo, abra o Delphi, adicione um
DataModule e logo em seguida um Sqlconnection e configure a conexão com o banco
de dados Firebird. Salve-o como DM. Pegue um SqlDataset altere seu nome para
Sqlpai e ligue-o ao Sqlconnection através de sua propriedade Sqlconnection.
Em sua
propriedade CommandText iremos colocar a instrução SQL para obter os campos da
tabela tbpai. Veja abaixo:
SELECT * FROM TBPAI
Neste momento colocaremos um
componente Datasetprovider e ligaremos a propriedade Dataset ao Sqlpai e em
seguida coloque um Clientdataset, nomeie para Clientpai e coloque em sua
propriedade providername como datasetprovider1.
Até aqui a configuração de nossos
componentes ocorreu normalmente, a partir daqui iremos montar o relacionamento
Pai, Filho e Neto.
Coloque um componente Sqldataset
e o nomeie para SqlFilho, ligue sua propriedade Sqlconnection para
Sqlconnection1 e em sua propriedade CommandText coloque o seguinte código SQL:
SELECT * FROM TBFILHO
WHERE TBFILHO.COD_PAI=:COD_PAI
Veja
que na instrução de SELECT coloquei uma clausula de WHERE com um parâmetro ao
qual será utilizada para fazer o relacionamento com a tabela tbpai, sendo assim
esse parâmetro deve ter o mesmo nome do campo da tabela Pai.
Em
seguida deveremos ligar este Sqlfilho com o Sqlpai e será feito isto através de
um datasource. Coloque no Datamodule um componente Datasource e dê o nome de
Dspai. Em sua propriedade dataset coloque como Sqlpai, no Sqlfilho coloque a
propriedade Datasource para Dspai. Coloque mais um componente Clientdataset e
altere seu nome para Clientfilho.
Veja a
figura abaixo como está ficando nosso Datamodule.
Figura 04. Datamodule da aplicação.
Até aqui nada
é novidade, iremos agora configurar o restante dos componentes que fazem parte
da tabela tbneto. O comportamento é praticamente o mesmo em se tratando de
Mestre/Detalhe. Verifiquem:
Coloquem em seu Datamodule mais
um Sqldataset com o nome de Sqlneto, altere sua propriedade Sqlconnection para
Sqlconnection1 e em seguida coloque a seguinte instrução SQL na propriedade
CommandText:
SELECT * FROM TBNETO WHERE TBNETO.COD_FILHO=:COD_FILHO
Nesta instrução SQL será
passado como parâmetro o COD_FILHO, não esquecendo que na cláusula WHERE o nome
dos parâmetros deverão ser iguais aos campos das referidas tabelas para assim o
relacionamento ser bem sucedido.
Insira um
Datasource e o nomeie para Dsfilho ligando-o sua propriedade Dataset para
Sqlfilho e no componente Sqlneto coloque a propriedade Dataset para Dsfilho e
em seguida coloque um Clientdataset com o nome de Clientneto.Percebam que o
comportamento é o mesmo do citado acima. Em seguida coloque três Datasources
com os seguintes nomes: DSClientpai, Dsclientfilho e DSClientneto e ligue-os
aos seus respectivos Clientdatasets. Veja abaixo como está ficando nosso
DataModule.
Figura 05. Datamodule da aplicação.
Neste
momento iremos adicionar os Tfields. Dê um duplo clique sobre o Sqlpai e
adicione os Tfields. Em seguida faça o mesmo para o Clientpai e perceba que ao adicionar os TFields no Clientpai verifique que foi criado
o campo do tipo DataSetField, com o nome de Sqlfilho. Esse campo irá trazer as
informações da tabela Filho, por esse motivo que não utilizaremos um componente
DataSetProvider para a tabela Filho. Em seguida iremos fazer o mesmo para o
Sqlfilho adicionando os Tfields (Deixe a propriedade Active como true do
Sqlpai) e logo após para o Clientfilho mude sua propriedade Datasetfield para
ClientpaiSqlfilho, adicionando os campos e percebam que foi criado também um
Datasetfield chamado Sqlneto.
Finalmente
vamos adicionar os Tfields para o Sqlneto (Deixe a propriedade Active como true
do Sqlfilho) e em seguida para o Clientneto coloque em sua propriedade
Datasetfield para ClientfilhoSqlneto e por final adicionando os Tfields. Dê um
save All em sua aplicação.
Informação importante: Deixe a
propriedade Active como False do Sqlpai.
Estamos
com o relacionamento Pai/Filho/Neto concluído. Iremos criar nossa aplicação.
Adicione um Formulário e altere sua propriedade Caption para Relacionamento
Pai/Filho/Neto e a Position para poScreenCenter. Adicione a unit DM para o
nosso formulário poder utilizá-las.
implementation
{$R *.dfm}
uses DM;
Arraste
para o formulário os campos COD_PAI e NOME_PAI do Clientdataset Clientpai e em seguida coloque
um Dbnavigator ligando-o ao Dm.DSClientpai e um Botão com a propriedade Caption
como Gravar.
Adicione
um Dbgrid e um Dbnavigator para a tabela tbfilho, ligue a propriedade
Datasource para Dm.DSClientfilho e adicione apenas três colunas no Dbgrid
(COD_FILHO, COD_PAI e NOME_PAI). Para a tabela tbneto faça o mesmo, insira um
Dbgrid e um Dbnavigator ligue a propriedade Datasource para DM.DSClientneto e
adicione quatro colunas (COD_NETO, COD_FILHO e NOME_NETO).
Figura 06. Formulário.
A
função abaixo servirá para Incrementar o Generator criado em nosso banco de
dados, coloque-a no DataModule:
Function Autoinc(Generator: string; Conexao: TSQLConnection): Integer;
var
//classe TSQLDataSet
sdsAutoInc :TSQLDataset;
begin
//Criar
sdsAutoInc :=
TSQLDataset.Create(nil);
try
//recebe a classe
TSQLConnection
sdsAutoinc.SQLConnection :=
Conexao;
//seleciona o valor do codigo + 1
da tabela do banco de dados Rdb$Database
sdsAutoinc.CommandText := 'Select
Cast(Gen_Id('+Generator+',1) as integer) as codigo from Rdb$Database';
sdsAutoinc.Open;
Result :=
sdsAutoInc.FieldByName('Codigo').AsInteger;
finally
sdsAutoInc.Close;
sdsAutoInc.Free;
end;
end;
No evento AfterInsert de ambos os
Clientdatasets iremos incrementar nossa chave primária, veja abaixo:
procedure TDm.CLientpaiAfterInsert(DataSet: TDataSet);
begin
if
ClientPai.State = dsInsert then
ClientPai.FieldByName('COD_PAI').AsInteger := Autoinc('GENPAI ',
Datamodule2.SQLConnection1);
end;
procedure TDm.ClientfilhoAfterInsert(DataSet:
TDataSet);
begin
if
ClientFilho.State = dsInsert then
ClientFilho.FieldByName('COD_FILHO').AsInteger := Autoinc('GENFILHO',
SQLConnection1);
end;
procedure TDm.ClientnetoAfterInsert(DataSet:
TDataSet);
begin
if
ClientNeto.State = dsInsert then
ClientNeto.FieldByName('COD_NETO').AsInteger := Autoinc('GENNETO',
SQLConnection1);
end;
Nesta
função Autoinc será passado dois parâmetros, o primeiro o nome do generator
criado para o código e o segundo o nome da conexão utilizada pelo Banco de
Dados.
Relembrando:
Controlando a atualização
dos dados no DBExpress
Essas
configurações que iremos realizar agora são muito importantes, pois serão utilizadas
em qualquer situação com o DBExpress, tanto numa conexão a uma simples tabela
quanto para a utilização num relacionamento Pai/Filho/Neto. Através dessas
configurações iremos controlar quais campos serão atualizados dentro do Delphi
e quais campos serão indicados na clausula Where das instruções de Update e
Delete enviadas para o banco de dados.
No
DataSetProvider na propriedade UpdateMode indica a forma como serão pesquisados
os registros no momento da atualização dos dados e contêm os valores upWhereAll,
upWhereChanged e upWhereKeyOnly. Utilizaremos em nosso projeto como
UpWhereKeyOnly. Em seguida no DatasetProvider na propriedade Options
setaremos poCascadedeletes e poCascadeUpdate para true, assim ao excluir ou
atualizar a tabela Pai ou a Filho os registros que estarão relacionados também
serão excluídos ou atualizados.
Importante: O ProviderFlags dos Tfields
serão configurados somente nos componentes Sqldataset pois o Datasetprovider
busca informações somente destes Tfields não sendo utilizados os ProviderFlags
existentes nos Tfields do Clientdataset.
A
propriedade ProviderFlags contém os valores pfInUpdate, pfInWhere, pfInKey e
pfHidden.
A
seguir iremos verificar como utilizar os valores dessas da propriedade
ProviderFlags.
pfInUpdate : Indica se o
campo poderá enviar valores para o banco de dados, através do Insert e
Update;
pfInWhere : Indica se o
campo será utilizado na clausula Where para encontrar o registro que está sendo
atualizado, isso quando o UpdateMode estiver com o valor upWhereAll ou
upWhereChanged do DataSetProvider.
pfInKey : Indica que o campo
é um campo chave primária e será utilizado para encontrar o registro que está
sendo atualizado, isso quando o UpdateMode estiver com o valor upWhereKeyOnly
do DataSetProvider.
pfHidden : Indica que a
coluna será utilizada somente para a localização dos registros.
Configurando
os Providerflags dos componentes SqlDataset
Sqlpai:
COD_PAI
|
NOME_PAI
|
pflnUpdate: true
|
pflnUpdate: true
|
pflnWhere: false
|
pflnWhere: false
|
pflnKey: true
|
pflnKey: false
|
pfHidden: false
|
pfHidden: false
|
Sqlfilho:
COD_FILHO
|
NOME_FILHO
|
COD_PAI
|
pflnUpdate: true
|
pflnUpdate: true
|
pflnUpdate: true
|
pflnWhere: false
|
pflnWhere: false
|
pflnWhere: false
|
pflnKey: true
|
pflnKey: false
|
pflnKey: false
|
pfHidden: false
|
pfHidden: false
|
pfHidden: false
|
Sqlneto:
COD_NETO
|
NOME_NETO
|
COD_FILHO
|
pflnUpdate: true
|
pflnUpdate: true
|
pflnUpdate: true
|
pflnWhere: false
|
pflnWhere: false
|
pflnWhere: false
|
pflnKey: true
|
pflnKey: false
|
pflnKey: false
|
pfHidden: false
|
pfHidden: false
|
pfHidden: false
|
Em ambos os
SqlDatasets e ClientDatasets altere a propriedade required dos campos chaves
como False, pois estes campos são incrementais gerados pelo generator e
incrementado através de nossa Função Autoinc.
Para finalizar
nosso projeto configuraremos o Botão para DM.Clientpai.Applyupdates(0) e em
seguida mostraremos uma mensagem Showmessage(‘Informações enviadas para o Banco
de Dados’) e no evento OnActivate do Form os seguintes códigos:
procedure TForm1.FormActivate(Sender: TObject);
begin
DM.ClientPai.Active:=true;
DM.ClientFilho.Active:=true;
DM.ClientNeto.Active:=true;
Dbedit2.SetFocus;
end;
Figura 07. Layout Final da
aplicação.
Conclusão
Escrevi
esta matéria com base em duas matérias publicadas em nossa revista chamadas:
“DBEXPRESS – Criando um relacionamento mestre – detalhe Parte I” do Mês de
Agosto de 2003 e “DBEXPRESS – Criando um relacionamento mestre – detalhe Parte
II” do Mês de Setembro de 2003. Apesar de serem matérias ditas antigas no nosso
suporte técnico ocorrem muitas dúvidas a respeito e devido a solicitação de
alguns sócios resolvi publicar esta matéria que trata de relacionamentos do
tipo Pai/Filho/Neto utilizando os componentes da Palheta DBEXPRESS. Muitas
dúvidas também são surgidas quanto a configuração dos ProviderFlags e do
DataSetProvider. Nesta matéria procurei relembrá-los. Espero que tenham gostado
e para os que já utilizam tenha acrescentado alguma coisa a mais. Um abraço a
todos e até a próxima.
Parabéns pelo post.
ResponderExcluirEra o que estava Faltando em meu projeto...