segunda-feira, 23 de janeiro de 2012

Delphi - DBEXPRESS – Criando um relacionamento Pai/Filho/Neto



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.                        

Um comentário: