Desenvolvimento - Delphi
Delphi: Programação Cliente/Servidor - parte 3
Neste artigo veremos como representar um relacionamento existente entre as tabelas do banco de dados nos datasets do nosso bom e “velho” Delphi, isto é, vamos criar um relacionamento entre datasets e explorar os benefícios que ele irá nos trazer.
por Flávio LambertiComo de costume nestes artigos, vamos manter um padrão, primeiro falando sobre a programação com query e depois falando sobre os SqlDataSets, DataSetProviders e ClientDataSets.
Começaremos introduzindo em um data module um objeto Query, partindo da premissa que já existem as tabelas no banco de dados com os seus respectivos relacionamentos, e as conexões ao banco de dados devidamente criadas (Alias na BDE e objeto Database conectado ao mesmo). Iremos renomear esta Query para QryPedidos e conectá-la ao database e, em sua propriedade SQL, iremos adotar a seguinte instrução:
Select Pedidos.* From Pedidos Where Pedidos.Id_Pedido = :parID
Em seguida iremos, como de costume de todos, acredito eu, transformar os campos da tabela em objetos dentro da QryPedidos (duplo click na Query e dentro do “fields editor” que se abrirá clique com o botão direito do mouse e escolher a opção “add all fields”), e por fim habilitar a propriedade “cachedupdates”, o que é imprescindível para o funcinamento do relacionamento. Ao habilitar esta propriedade você está informando ao DataSet que todas as alterações realizadas farão efeito apenas na memória do DataSet e não diretamente no banco de dados.
O próximo passo é colocar no data module um DataSource que irá se chamar DsPedidos e será conectado a QryPedidos. Este Datasource servirá de ponte entre as duas querys.
Introduza uma outra Query e chame-a de QryItens, conecte ao database, e em sua propriedade DataSource iniciaremos o relacionamento, indicando o Datasource que está ligado ao outro DataSet (DsPedidos). A propriedade “cahedupdates” desta query também deve estar habilitada. O próximo passo é indicar a instrução sql de acesso na propriedade SQL:
Select Itens.* From Itens Where Itens.Id_Pedido = :Id_Pedido
Você achou estranho esta condição criada na instrução sql ? Deve estar se perguntando : Se é pra colocar um parâmetro para filtrar os itens do pedido via código eu não precisaria ter este trabalho todo ? Então eu lhe responderei, este parâmetro será automaticamente alimentado por causa do relacionamento entre as Querys através do DataSource, isto é, sempre que for filtrado um pedido na QryPedidos, automaticamente os itens deste pedido serão filtrados por conta do relacionamento. Isto não quer dizer que, em uma eventual manipulação (inclusão), os itens serão automaticamente associados ao pedido em questão, a passagem do número do pedido para o item deverá ser feito manualmente, isto geralmente é realizado no evento before post da QryItens.
Outro ponto muito importante a ser abordado neste relacionamento será no momento de se aplicar, no banco de dados, as alterações realizadas na memória da máquina cliente, já que nossa query está trabalhando com cachedupdates habilitado. A rotina abaixo enfatiza perfeitamente este momento:
QryItens.DataSource := nil; If QryPedidos.State in [dsInsert, dsEdit] then begin QryPedidos .ApplyUpdates; QryItens.ApplyUpdates; end else begin While not QryItens.Eof do QryItens .Delete; QryPedidos.Delete; QryItens.ApplyUpdates; QryPedidos .ApplyUpdates; end; QryItens.DataSource := DsPedidos;
A primeira linha deste código (QryItens.DataSource := nil;) indica que estamos desabilitando o relacionamento entre as Querys, porque a Query, quando trabalhando em memória (cachedupdates = true), se atualiza automaticamente após o envio das alterações (inclusões, alterações e/ou exclusões) da memória da Query para o banco de dados.
Por exemplo: Caso eu envie uma inclusão para o banco de dados com o relacionamento ainda vigorando, a QryPedidos se atualizaria automaticamente após este envio (QryPedidos .ApplyUpdates;) e, devido ao relacionamento, os itens daquele pedido seriam retornados para QryItens, isto é, não retornaria nenhum registro para a QryItens porque, no banco de dados, ainda não existe nenhum item cadastrado para o mesmo e a QryItens seria esvaziada, quer dizer, os registros existentes no momento da inclusão seriam substituídos pelos registros retornados do banco de dados (nenhum), sendo assim perdidos os registros que o usuário havia cadastrado.
Por este motivo devemos desfazer o relacionamento entre as Querys antes de enviar os dados para o banco de dados e, após o envio destes dados, realizar a operação inversa, refazendo o relacionamento, o que é feito na última linha do procedimento (QryItens.DataSource := DsPedidos;).
Continuando a análise do código apresentado, devemos também ressaltar a ordem de envio dos dados ao banco de dados, pois existe um relacionamento entre estas tabelas no banco de dados, o que nos obriga a seguir uma sequência de inclusão e exclusão de registros.
Em uma inclusão devemos enviar primeiro os dados da tabela pedidos (PAI), pois o relacionamento no banco de dados através de uma Foreign-Key (chave-estrangeira) nos diz que um Item (FILHO) só pode ser gravado relacionando-o a um pedido (PAI) que já exista, e em uma exclusão é feito o contrário, pois este relacionamento também diz que um pedido (PAI) que tem itens (FILHO) relacionados a ele não pode ser excluído, portanto devemos excluir primeiro da tabela de itens, o que é perfeitamente feito na rotina acima.
Agora que terminamos a parte onde explanamos o relacionamento entre Querys irei agora demonstrar como seria realizado este relacionamento entre os objetos da palheta DbExpress (Delphi 6, 7), para tanto deveremos ter em nosso data module, além do objeto SqlConnection devidamente configurado, um objeto SqlDataSet renomeado para SqlPedidos e outro renomeado para SqlItens e, em suas propriedades ComandText as mesmas instruções utilizadas nas querys. A exemplo do relacionamento entre querys o SqlItens deverá estar conectado ao DataSource do SqlPedidos através da propriedade DataSource.
Até o momento não vimos nenhuma diferença do relacionamento entre querys e o relacionamento entre os objetos da palheta DbExpress, porém estas diferenças se inicarão agora ao colocarmos apenas um DataSetProvider em nosso data module, onde o mesmo será renomeado para DspPedidos e se conectará ao SqlPedidos, assim como indica o nome. O SqlItens não precisará de um DataSetPrivider ligado a ele e já já vocês verã o por quê.
Agora precisamos em nosso data module de um ClientDataSet o qual será chamado de CdsPedidos, ligado ao DspPedidos e terá os campos transformados em objetos dentro dele (duplo click no CdsPedidos, clicar com o botão direito do mouse dentro do “fields editor” que se abriu e escolher a opção “add all fields”). Ao fazer esta última operação verificamos que além dos campos da tabela de pedidos (que foram selecionados no SqlPedidos) também foi criado um datafield chamado SqlItens, este é o próprio SqlItens transformado em um datafield por conta do relacionamento.
Então, colocaremos um novo ClientDataSet e o chamaremos de CdsItens, e não precisaremos o conectar a nenhum DataSetProvider e sim, na propriedade DataSetFields, escolher o CdsPedidos.SqlItens. Tranforme os campos em objeto dentro do CdsItens e contemplem as maravilhas do Delphi.
Uma das vantagens do relacionamento entre os SqlDataSets é que não precisamos alimentar o campo de relacionamento no dataset filho. No relacionamento entre querys nós eramos obrigados, no evento before post da query filho (QryItens), a indicar para o registro (item) a qual pedido ele pertencia, o que não acontece neste modelo, este preenchimento é automático. E mais uma vantagem deste modelo é que o relacionamento não precisa ser desfeito em nenhum momento, mesmo na hora da gravação o relacionamento pode ser mantido que não haverão problemas.
O código de confirmação das operações seria simplesmente assim :
If not (CdsPedidos.State in [dsInsert, dsEdit]) then begin While not CdsItens.Eof do CdsItens.Delete; CdsPedidos.Delete; end; CdsItens.ApplyUpdates(0); CdsPedidos.ApplyUpdates(0); end;
Onde o método ApplyUpdates se encarrega de levar as alterações realizadas em memória para serem realizadas no banco de dados, sendo que este método necessita da passagem de um parâmetro que se refere ao número máximo de erros que o mesmo irá suportar durante a operação. Geralemente é passado como parâmetro o valor de zero, para que a operação só seja efetuada quando não ocorrer nenhum erro.
Espero ter sido bastante claro nas esplicações sobre estes relacionamentos entre DataSets, e que todos os leitores façam um bom proveito dos conhecimentos obtidos através da leitura deste artigo, E aguardem o próximo artigo que provavelmente será o último da série sobre Programação Cliente/Servidor.
Para os que não conhecem os conceitos de bancos de dados cliente/servidor (SGBD) aconcelho que leiam a metéria sobre este assunto nesta mesma sessão do site.
Quaisquer dúvidas sobre esta e outras matéria escritas por mim, entrem em contato pelo email.
Abraços
Flávio Lamberti