[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

2 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!

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.