[Delphi] Afinal, qual é a melhor forma de copiar registros de um DataSet?

[Delphi] Afinal, qual é a melhor forma de copiar registros de um DataSet?

Saudações, leitores!
O artigo de hoje traz uma dúvida relativamente comum. Eventualmente, por conta das regras de negócio do cliente ou uma migração de dados, surge a necessidade de copiar vários registros de um DataSet para outro. Neste momento, uma das nossas maiores preocupações é a performance dessa operação, concordam? Confira, neste artigo, algumas formas de realizar essa cópia e a apresentação de dois ótimos recursos que o FireDAC nos oferece para essa finalidade.

 

Algumas vezes recebo e-mails com a seguinte dúvida:

André, preciso copiar vários registros entre dois DataSets. Qual a melhor forma? Executar um loop e copiar cada campo através do FieldByName?

O motivo que causa essa dúvida, a princípio, está relacionado ao requisito de desempenho da rotina. Os programadores buscam a melhor forma de codificação para reduzir, ao máximo, o tempo dispendido pela operação, afinal, quando nos referimos a estruturas de repetição, devemos ter cautela nas instruções dentro da iteração para não comprometer a experiência do usuário.
Por conta disso, como forma de contribuição para a comunidade Delphi, fiz questão de abordar este assunto no blog.
Para que o artigo fique em uma estrutura didática, cada solução foi dividida em diferentes seções, ordenadas por recomendação em ordem crescente. Vamos lá!

 

1) Executar um loop no DataSet, copiando os valores com FieldByName
A primeira forma é utilizar um loop para percorrer os registros do DataSet, copiando os valores de cada campo através do método FieldByName:

DataSetOrigem.First;
while not DataSetOrigem.Eof do
begin
  DataSetDestino.Append;
 
  DataSetDestino.FieldByName('Campo1').Value := DataSetOrigem.FieldByName('Campo1').Value;
  DataSetDestino.FieldByName('Campo2').Value := DataSetOrigem.FieldByName('Campo2').Value;
  {...}
  DataSetDestino.FieldByName('CampoN').Value := DataSetOrigem.FieldByName('CampoN').Value;
 
  DataSetDestino.Post;
 
  DataSetOrigem.Next;
end;

Há alguns anos, foram publicados alguns artigos na internet com orientações para evitar o uso do FieldByName, já que, até então, este método realizava um loop em uma lista de objetos do DataSet para encontrar o campo desejado. Porém, desde o Delphi Seattle, a lista que armazena os Fields do DataSet foi substituída pelo TDictionary, que possui uma performance evidentemente melhor. Apenas para título de conhecimento, essa alteração pode ser encontrada na classe TFields:

FDict: TDictionary<string, TField>;

Portanto, não se preocupe mais com desempenho ao utilizar o FieldByName. 🙂

 

2) Executar um loop no DataSet, copiando os valores com variáveis TField
Mesmo assim, se você prefere evitar o uso do FieldByName, existe a opção de criar variáveis do tipo TField e apontá-las para os campos do DataSet antes de iniciar as iterações:

var
  Campo1: TField;
  Campo2: TField;
  {...}
  CampoN: TField;
begin
  Campo1 := DataSetDestino.FieldByName('Campo1');
  Campo2 := DataSetDestino.FieldByName('Campo2');
  {...}
  CampoN := DataSetDestino.FieldByName('CampoN');
 
  DataSetOrigem.First;
  while not DataSetOrigem.Eof do
  begin
    DataSetDestino.Append;
 
    Campo1.Value := DataSetOrigem.FieldByName('Campo1').Value;
    Campo2.Value := DataSetOrigem.FieldByName('Campo2').Value;
    {...}
    CampoN.Value := DataSetOrigem.FieldByName('CampoN').Value;
 
    DataSetDestino.Post;
 
    DataSetOrigem.Next;
  end;  
end;

O mesmo pode ser feito para os campos de origem, mas, neste caso, teríamos o dobro de variáveis do tipo TField.

 

3) Executar um loop nos Fields para evitar a repetição de código
A terceira opção é usar um loop dentro de um loop. O primeiro itera os registros e o segundo itera os Fields do DataSet com uma instrução FOR-IN. Dessa forma, evitamos a necessidade de escrever uma linha para cada campo, tornando-se um benefício quando os DataSets possuem dezenas de campos a serem copiados:

var
  Field: TField;
begin
  DataSetOrigem.First;
  while not DataSetOrigem.Eof do
  begin
    DataSetDestino.Append;
 
    for Field in DataSetOrigem.Fields do
      DataSetDestino.Fields[Field.Index].Value := Field.Value;
 
    DataSetDestino.Post;
 
    DataSetOrigem.Next;
  end;
end;

Vale ressaltar que essa opção só é viável quando os dois DataSets possuem a mesma estrutura de Fields, inclusive na mesma ordem. Caso contrário, os valores de alguns campos no DataSet de destino poderão ficar inconsistentes, já que foram copiados de campos diferentes.

 

4) FireDAC: usando o método CopyRecord
Além da enorme quantidade de vantagens proporcionadas, a tecnologia FireDAC também trouxe o método CopyRecord, disponível para copiar todos os valores do registro atual de um DataSet. Acompanhe:

DataSetOrigem.First;
while not DataSetOrigem.Eof do
begin
  DataSetDestino.Append;
  DataSetDestino.CopyRecord(DataSetOrigem);
  DataSetDestino.Post;
 
  DataSetOrigem.Next;
end;

Não precisamos utilizar o FieldByName ou variáveis TField, sem contar que, claro, o código fica bem mais limpo. Além disso, o próprio método se encarrega de copiar apenas os campos correspondentes, caso a estrutura dos DataSets não seja equivalente.

 

5) FireDAC: o poderoso método CopyDataSet
Se eu disser que podemos executar essa cópia com apenas uma instrução, vocês acreditariam? Pois bem, o método CopyDataSet copia todos os valores de todos os registros de um DataSet, respeitando a mesma característica de campos correspondentes:

DataSetDestino.CopyDataSet(DataSetOrigem);

Um grande diferencial deste método é a performance. Em um teste rápido, usando um DataSet com 4 campos e 5.000 registros, o loop com CopyRecord levou 6.58 segundos, enquanto o CopyDataSet demorou 0.04.

 

Menos de 1 segundo?!
Exato, meu amigo! Migre já para o FireDAC! 🙂

 

Fico por aqui, pessoal.
Qualquer dúvida, observação ou contribuição, não hesite em deixar um comentário.
Abraço!


 

Compartilhe!
Share on FacebookTweet about this on TwitterShare on LinkedInShare on Google+Pin on PinterestEmail this to someone

12 comentários

    1. Olá, Carlos!
      O CopyDataSet faz uma operação similar à atribuição da propriedade Data, mas com algumas diferenças. Entre elas, como já apresentei no artigo, o método copia apenas os campos correspondentes, ao invés de sobrescrever a estrutura do DataSet de destino, como acontece na atribuição do Data. Além disso, o CopyDataSet não mantém as “versões” dos registros de origem, ou seja, não copia as informações de que os registros foram inseridos, removidos ou alterados.
      Não abordei no artigo, mas o CopyDataSet aceita um conjunto de opções como segundo parâmetro. É possível, por exemplo, copiar a estrutura do DataSet de origem antes da operação, e/ou também copiar índices, campos agregados e constraints, conforme a necessidade do desenvolvedor.
      No link abaixo, do Wiki da Embarcadero, há mais detalhes deste método:

      http://docwiki.embarcadero.com/Libraries/Berlin/en/FireDAC.Comp.DataSet.TFDDataSet.CopyDataSet

      Abraço!

    1. Olá, Ramon!
      Pretendo postar um artigo exclusivo sobre os Options do FireDAC, mas, em resumo, essas são as opções disponíveis:

      • coStructure: copia a estrutura do DataSet de origem;
      • coIndexesCopy: copia os índices do DataSet de origem. O inverso é coIndexesReset;
      • coAggregatesCopy: copia os campos agregados (Aggregates) do DataSet de origem. O inverso é coAggregatesReset;
      • coConstraintsCopy: copia as constraints do DataSet de origem. O inverso é coConstraintsReset;
      • coRestart: move o cursor para o primeiro registro do DataSet de destino após a cópia (padrão);
      • coAppend: adiciona cada registro do DataSet de origem no DataSet de destino (padrão);
      • coEdit: localiza o registro no DataSet de destino para editá-lo;
      • coDelete: caso um registro tenha sido removido no DataSet de origem, localiza o registro correspondente no DataSet de destino para também removê-lo;
      • coRefresh: alterar os registros somente no cache local, diferente do coEdit.

      Para mais detalhes, acesse:

      FireDAC CopyDataSet at Embarcadero Wiki

      Abraço!

  1. Opa André!

    É possível fazer um ApplyUpdates com o DataSet de destino após o CopyDataSet?

    Tipo:

    FDQueryDestino.EmptyDataSet;
    FDQueryDestino.CopyDataSet(FDQueryOrigem);
    FDQueryDestino.ApplyUpdates(0);
    1. Boa noite, Wanderson!
      Não cheguei a fazer a teste, mas, pela proposta da função, é como se os registros estivessem sido inseridos um a um no DataSet de destino, portanto, o ApplyUpdates deveria funcionar.

      Abraço!

  2. Eu testei e funciona perfeitamente… eu estava tentando fazer de um SGDB pra Outro mas estava dando problemas e achei que poderia ser uma limitação…

  3. Essas funções não aparacem no componente do ClientDataSet, a unica função disponivel é a “CopyFields”.
    estou usando o delphi Xe7

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Preencha o campo abaixo * Time limit is exhausted. Please reload CAPTCHA.