quinta-feira, 9 de junho de 2011

Relacionamento Master Detail utilizando DataSetField

Nesse artigo vou mostrar como criar um relacionamento Master/Detail utilizando o recurso DataSetField do ClientDataSet.
Para podermos criar no Delphi uma relação Master/Detail em uma única estrutura de memória, devemos utilizar datasets aninhados, ou seja, o resultset traz em um dataset os TFields da tabela master e um TField expecial chamado TDataSetField, que representara a tabela detail.
Para podemos acessar o conteúdo de um TDataSetField, devemos usar a propriedade DataSetField de um componente dataset(no nosso caso o ClientDataSet).

Irei mostrar através de uma aplicação simples, como fazer esse relacionamento utilizando os recursos do dbexpress e do ClientDataSet.

Vamos à prática:

-Nosso banco para esse teste será o EMPLOYEE do firebird, segue seu caminho:  C:\Program Files\Firebird\Firebird_2_5\examples\empbuild\EMPLOYEE.FDB
-Crie uma aplicação nova: File > New > VCL Form Application.

-Adicione os seguintes componentes:

-TSQLConnection (configure uma conexão com o banco EMPLOYEE)
Name: sqlEmployee
-TSQLQuery
Name: qryClientes (será o MASTER)
SQL: select * from CUSTOMER
SqlConnection: sqlEmployee
-TSQLQuery
Name: qryVendas(será os DETAILS)
SQL: select * from SALES where CUST_NO = :CUST_NO
SqlConnection: sqlEmployee
Params: Configure o parâmetro CUST_NO: DataType = ftInteger e ParamType = ptInput

Nota: O nome do parâmetro deve ser igual ao nome do campo da PK na tabela Master.

Para configurar o relacionamento master/detail em apenas uma estrutura de memória vamos adicionar um TDataSetProvider apontando para a qryClientes(MASTER),  um TClientDataset apontando para o TDataSetProvider e um TDataSource apontando para a qryClientes:

- TDataSetProvider
Name: dspMaster
DataSet: qryClientes

- TClientDataset
Name: cdsClientes
ProviderName: dspMaster

- TDataSource
Name: dtsMaster
DataSet: qryClientes

Nota: Acesse a propriedade DataSource da qryVendas e aponte para dtsMaster.

Neste Momento já possuímos a estrutura master/detail montada, adicionando os TFields no cdsClientes podemos ver os campos da qryClientes e um campo do tipo TDataSetField,  que representa os dados da qryVendas.
Para testarmos esse relacionamento adicione os seguintes componentes:

- TDataSource
Name: dtsClientes
DataSet: cdsClientes

- TClientDataset
Name: cdsVendas
ProviderName: dspMaster
DataSetField: cdsClientesqryVendas

Nota: Adicione os TFields no cdsVendas.

- TDataSource
Name: dtsVendas
DataSet: cdsVendas

Nota: Para que o DataField qryVendas fique visível apenas no TDBGrid de Vendas, deixe esse campo invisível no cdsClientes.

-TDBGrid (Master)
Name: dbgClientes
DataSource: dtsClientes

-TDBGrid (Detail)
Name: dbgVendas
DataSource: dtsVendas

Se tudo foi feito corretamente é só abrir o cdsClientes, rodar a aplicação e conferir o dbgClientes exibindo os dados do cliente e o dbgVendas exibindo suas respectivas vendas.  
Note que se forem alterados dados do cliente e/ou de suas vendas, apenas um ApplyUpdates é necessário para enviar em um único datapacket as alterações para o DataSetProvider. Isso é possível porque o DataSetProvider tem a capacidade de distribuir essas alterações entre as querys(qryClientes e a qryVendas) atualizando assim as duas tabelas.

Pronto! Uma forma prática e muito útil principalmente para aplicações client/Server de se criar um relacionamento Master/Detail.

Qualquer dúvida ou sugestão é só deixar um comentário ou enviar um e-mail.
Até a próxima, obrigado.

17 comentários :

  1. Em que evento e objeto ficaria o ApplyUpdates ?? Tem como adicionar botão pra Inclui/Alterar/Excluir registro Master x Detail ??

    ResponderExcluir
    Respostas
    1. Olá Mauro, obrigado pelo comentário e peço desculpas dela grande demora, pois, o blog estava desativado.

      Você pode incluir um botão 'Confirma', nesse botão chame o ApplyUpdates do cdsCliente, o DataSetProvider se encarrega de distribuir as alterações efetuadas nas duas tabelas(nos dois cds).

      Lembrando que para incluir, deletar, ou excluir registro do cdsVendas, você deve faze-lo no cdsVendas.

      Exemplo de alterações:

      deletar uma venda:
      cdsVendas.Delete;

      deletar um cliente que não tenha mais venda:
      cdsCliente.Delete;

      persistir alterações no banco:
      cdsClientes.ApplyUpdates(0);

      Quaqluer dúvida comente novamente,

      Obrigado.

      Excluir
    2. quando eu utilizar o metodo cdsClientes.Append; tem como ele ficar com o preenchimento do id automatico??

      Excluir
    3. Mauro, tem sim, seu campo é autoincremento no banco?

      Excluir
  2. Respostas
    1. Olá Paulo,
      O blog esta sendo reativado depois de uma grande tempo sem atualizações!

      Espero contar com seus comentários!

      Obrigado.

      Excluir
  3. Caro Paulo
    estou com um problema com master/detail utilizando clientdataset no delphi 7 e oracle

    os datasets estão montados corretamente, mas no momento de usar o comando applyupdates, o dataset detail não esta sendo salvo, dando erro de constraint no dataset master

    o que pode ser isso

    ResponderExcluir
    Respostas
    1. errei , não é Paulo é Rafael
      hehehe

      Excluir
    2. Olá Edson,

      Para consulta seu relacionamento está ok? o problema é apenas na atualização?

      Qual é o tipo da constrant que está retornando erro? (PK, FK, Unique, Check, Not Null)

      Verifique se os provider flags estão configurados corretamente nos SQLQuerys/SQLDatasets e nos ClientDataSets.

      Qualquer coisa, deixe outro comentário.

      Obrigado

      Excluir
  4. Muito bom o artigo!
    Entendi muita coisa agora.
    Só uma dúvida:
    Eu não poderia ao invés de apontar o dtsMaster para uma query apontá-lo direto para o ClientDataSet da consulta Master?
    Obrigado...
    Excelente artigo...

    ResponderExcluir
    Respostas
    1. Olá Leandro, se vc fizer isso o campo do tipo TDataSetField não irá aparecer na lista de TFields do cdsClientes.

      Obrigado pelo comentário.

      Excluir
  5. Rafael, bom dia e obrigado pelo seu post me ajudou muito
    Gostaria de saber como utilizar o mesmo metodo com Pai, Filho e neto :)

    ResponderExcluir
  6. Prezado Rafael,

    não consigo passar desta parte (adicionar os fields no clientdataset)

    TDataSource - na propriedade DataSet -se deixo configurado para SQLQuery (qryClientes) aparece o seguinte erro:

    SQLConnection property requerid for this operation.

    se apagar a propriedade dataset do tdatasource, consigo colocar os fileds no clientdataset (cdsClientes).

    já fiz buscas na internet e com outro tutorial, aconteceu a mesma coisa.
    o que pode ser isto.
    uso delphi xe5 + firebird 2.5

    ResponderExcluir
  7. Rafael, bom dia e agradeço muito por esta publicação. está me ajudando muito. Quanto ao erro anterior que postei, pode dar ultima forma, consegui corrigir, é que não tinha setado a propriedade sqlconnection do qyrVendas. Tá tudo bem agora.

    ResponderExcluir
    Respostas
    1. Olá Nilton,
      Que bom que conseguiu resolver, qualquer dúvida só entrar em contato.
      Obrigado.

      Excluir
  8. Veja se você pode me auxiliar, estou com a seguinte situação:

    Eu tenho 1 TSQLQuery (sqlPai) com 1 TClientDataSet (qryPai), 1 TDataSetProvider (dspPai) e 1 TDataSource (dtsPai)

    Com esse cabeçalho, eu quero fazer o seguinte:
    Tenho:
    1 TSQLQuery (sqlFilho) com 1 TClientDataSet (qryFilho), 1 TDataSetProvider (dspFilho) e 1 TDataSource (dtsFIlho), com mastersource no dtsPai, apontando o ID_PAI como MasterField/IndexFieldName

    Após este filho eu tenho um::
    1 TSQLQuery (sqlNeto) com 1 TClientDataSet (qryNeto), 1 TDataSetProvider (dspNet) e 1 TDataSource (dtsNeto), com mastersource no dtsFilho, apontando o ID_FILHOcomo MasterField/IndexFieldName.

    Aí tenho o seguinte cenário, a qryPai está aberta, ao executar o qryFilho.Open ele me exibe a seguinte mensagem:

    ORA-01036: nome/número de variável inválido

    Não estou usando parâmetros nos selects, porém tenho as colunas informadas no WHERE.

    Tentei fazer o seguinte daí, tirei o qryFilho de mastersource e fiz ele pegar o parâmetro no beforeopen, de acordo com o ID_PAI presente no qryPai, abriu corretamente a qryFilho, porém ao tentar abrir a qryNeto, o mesmo erro é exibido:

    ORA-01036: nome/número de variável inválido


    Você tem alguma ideia do que possa estar errado nessa relacionamento mastersource?

    ResponderExcluir
  9. Bom dia!

    Poderiamos utilizar um exemplo em MYSQL? Você já utilizou esta técnica? Tenho tido problemas com grande volume de dados em MESTRE/DETALHE.

    Um abraço.

    ResponderExcluir