Thursday, November 19, 2009

Faça sua aplicação ADO + DataSnap Voar, revisited!

Tenho andado sem tempo de postar, mas vamos lá.
A minha modificação referente ao meu post anterior sobre melhoria de performance no DataSnap, mais especificamente quando está obtendo dados de um DataSet ADO (dbGo), é chamar o DisableControls do DataSet ANTES de obter os dados.
Porquê isto tem significativo impacto na performance do ADO? A resposta está na unit ADODB.pas mais especificamente no método InternalGetRecord, como mostrado abaixo:


if (BookmarkSize > 0) and ((adRecDeleted and RecordStatus) = 0) then
begin
BookmarkFlag := bfCurrent;
Bookmark := Recordset.Bookmark;
if ControlsDisabled then
RecordNumber := -2 else
RecordNumber := Recordset.AbsolutePosition;
end else
BookmarkFlag := bfNA;


Note que no interior do método, um trecho do código que SEMPRE é executado contém "if ControlsDisabled". Quando ControlsDisabled retorna FALSE, é chamado o método AbsolutePosition do Recordset que é lentoooooooo, muito lento!

Durante o processo de prover os registros do ADODataSet não é necessário saber a posição absoluta no RecordSet, portanto não há necessidade nenhuma disso!

Teríamos duas soluções: Editar o ADODB.pas uma vez que o método InternalGetRecord não é virtual ou dinâmico (não cabendo então override), ou chamar DisableControls na mão, antes de abrir o ADODataSet a partir do DataSet provider.

Preferi não mexer no ADODB.pas (por enquanto!), e sim criar um descendente do DataSetProvider, chamado por mim de TDataSetProviderEx, que chamasse explicitamente o DisableControls ANTES de prover os registros.

Um porém é: Não se pode chamar DisableControls em um DataSet master que participa de uma relação Master-Detail, pois o detalhe não ficaria sincronizado com o master. Então, como fazer? Bem, a minha solução basicamente está em fazer um novo método InternalGetRecords do DataSetProviderEx, como abaixo:


function TDataSetProviderEx.InternalGetRecords(Count: Integer; out RecsOut: Integer;
Options: TGetRecordOptions; const CommandText: WideString;
var Params: OleVariant): OleVariant;
var
CanDisableControls: boolean;
begin
CanDisableControls := DSCanDisableControls;
if CanDisableControls then
DataSet.DisableControls;
try
Result := inherited InternalGetRecords(Count, RecsOut, Options, CommandText, Params);
finally
if CanDisableControls then
DataSet.EnableControls;
end;
end;


onde DSCanDisableControls é o método:


function TDataSetProviderEx.DSCanDisableControls: boolean;
begin
Result := SmartDisableControls and not IsMaster;
end;


SmartDisableControls é uma propriedade que incorporei à classe TDataSetProviderEx e indica se é desejável tentar chamar DisableControls antes de prover os registros ou não. Esta propriedade é TRUE por default pois não vejo motivo para não chamar DisableControls antes. O método IsMaster verifica se existem detalhes do DataSet, ou seja, se o DataSet participa como MASTER numa relação Master/Detail:


function TDataSetProviderEx.IsMaster: boolean;
var
List: TList;
begin
Result := False;
if Assigned(DataSet) then
begin
List := TList.Create;
try
DataSet.GetDetailDataSets(List);
Result := List.Count > 0;
finally
List.Free;
end;
end;
end;


Caso o DataSet não seja Master e caso a propriedade SmartDisableControls for TRUE, então DisableControls será chamado antes de se prover os registros. O ganho de performance é ENORME em DataSets ADO. Em alguns casos que eu testei chegou a 500% de ganho (DataSets com 30.000 registros ou um pouco mais). O ganho é tão mais perceptível quanto maior for o DataSet ADO que proverá os registros.

A minha classe TDataSetProviderEx já está em vários sistemas em produção, entre eles Windows Services rodando 24x7, há pelo menos 6 meses, ou seja, funciona! E o ganho de performance é considerável! Lembrando: O ganho de performance ocorre para DataSets que usam ADO (dbGo) do Delphi. DBExpress e outros mecanismos de acesso à dados não possuem este "defeito de nascença" e portanto não serão afetados em sua performance.

Agora só falta acabar de empacotar o código do DataSetProviderEx e fazer o seu upload! :-)