Master-Detail no Delphi com FireDAC: vinculando dois DataSets

O relacionamento Master-Detail é muito utilizado em sistemas para apresentar dados que possuem uma relação de dependência. Alguns exemplos comuns são pedidos e seus itens, clientes e seus endereços ou notas fiscais e seus produtos.

Neste artigo, veremos como configurar esse relacionamento no Delphi utilizando o FireDAC. O foco principal será o vínculo entre os dois DataSets, feito com apenas três propriedades.

O projeto completo utilizado neste exemplo está disponível no GitHub: Code4Delphi/Master-Detail — Master Detail Delphi

O que é um relacionamento Master-Detail?

No relacionamento Master-Detail, um registro do DataSet mestre pode estar relacionado a vários registros do DataSet detalhe.

No exemplo deste artigo:

  • FDMemTableMaster representa os registros mestres;
  • FDMemTableDetail armazena os registros de detalhe;
  • o campo FDMemTableMaster.Id identifica o registro mestre;
  • o campo FDMemTableDetail.id_master informa a qual registro mestre cada detalhe pertence.

Assim, quando o usuário navega pelos registros do mestre, o FireDAC apresenta automaticamente apenas os detalhes relacionados ao registro atual.

Componentes utilizados

O formulário do exemplo possui dois TFDMemTable, dois TDataSource e dois TDBGrid:

  • FDMemTableMaster → DataSourceMaster → DBGrid1;
  • FDMemTableDetail → DataSourceDetail → DBGrid2.

Os dados são criados em memória apenas para facilitar a demonstração. O mesmo conceito pode ser aplicado a outros DataSets do FireDAC, de acordo com a estratégia utilizada para carregar os dados.

📍 Configurando o vínculo Master-Detail 📍

O ponto principal do exemplo está no método ConfigurarVinculoMasterDetail:

procedure TMasterDetailView.ConfigurarVinculoMasterDetail;
begin  
  FDMemTableDetail.MasterSource := DataSourceMaster;
  FDMemTableDetail.MasterFields := 'Id';
  FDMemTableDetail.IndexFieldNames := 'id_master';
end;

Estamos definindo as propriedades via código, mas você também pode fazer isso de forma RAD, diretamente pelo Object Inspector.

MasterSource

FDMemTableDetail.MasterSource := DataSourceMaster;

A propriedade MasterSource pertence ao DataSet detalhe. Ela indica qual TDataSource fornece o registro mestre atual.

Como DataSourceMaster está conectado ao FDMemTableMaster, o FireDAC consegue acompanhar a navegação realizada no DataSet mestre.

MasterFields

FDMemTableDetail.MasterFields := 'Id';

A propriedade MasterFields informa qual campo do DataSet mestre será relacionado ao campo definido em IndexFieldNames no detalhe.

IndexFieldNames

FDMemTableDetail.IndexFieldNames := 'id_master';

A propriedade IndexFieldNames é configurada no DataSet detalhe e informa qual campo será utilizado para localizar e organizar os registros relacionados ao mestre.

Neste exemplo, id_master funciona como a chave de ligação dentro de FDMemTableDetail. Cada registro de detalhe possui nesse campo o Id do respectivo registro mestre.

Portanto, o vínculo deste exemplo pode ser representado da seguinte forma:

DataSet mestreDataSet detalhe
FDMemTableMaster.IdFDMemTableDetail.id_master

Em outras palavras, o FireDAC mantém visíveis no detalhe somente os registros em que id_master possui o mesmo valor do campo Id do registro mestre atual.

Como o vínculo funciona durante a navegação

Imagine que o registro atual de FDMemTableMaster possui Id = 5. O FireDAC utiliza esse valor para selecionar, em FDMemTableDetail, somente os registros cujo campo id_master também seja igual a 5.

Ao navegar para o mestre de Id = 6, o conteúdo apresentado no DBGrid2 é atualizado automaticamente. Não é necessário percorrer os registros manualmente nem aplicar um novo filtro a cada alteração do registro mestre.

Inserindo os dados de demonstração

O método InserirDadosTemp cria os dois DataSets em memória e adiciona os registros utilizados no exemplo:

FDMemTableMaster.CreateDataSet;
FDMemTableDetail.CreateDataSet;

Para cada registro mestre, o código gera de três a dez registros de detalhe. O valor de LMasterId é gravado no campo id_master, estabelecendo a relação entre eles:

FDMemTableDetail.AppendRecord([LDetailId, LMasterId, LQuantidade, LUnitario, LDetailTotal]);

Depois de inserir os detalhes, o registro mestre é adicionado com seu identificador e valor total:

FDMemTableMaster.AppendRecord([LMasterId, LMasterTotal]);

Ordem de inicialização

No evento OnCreate do formulário, os dados são criados antes da configuração do relacionamento:

procedure TMasterDetailView.FormCreate(Sender: TObject);
begin
  Self.InserirDadosTemp;
  Self.ConfigurarVinculoMasterDetail;
end;

Essa ordem garante que os TFDMemTable já estejam criados e com seus campos disponíveis quando o vínculo for configurado.

⭐ Relacionamentos com mais de um campo

Caso o relacionamento utilize uma chave composta, os campos podem ser informados separados por ponto e vírgula e na mesma ordem:

FDMemTableDetail.IndexFieldNames := 'id_empresa;id_pedido';
FDMemTableDetail.MasterSource := DataSourceMaster;
FDMemTableDetail.MasterFields := 'id_empresa;id';

Nesse cenário, id_empresa do detalhe corresponde a id_empresa do mestre, enquanto id_pedido corresponde a id.

⬇️ Download do exemplo

Baixe o projeto, execute o exemplo e navegue pelos registros do primeiro grid para acompanhar a atualização automática dos detalhes:

Acessar o exemplo Master-Detail no GitHub