Uso pleno do TClientDataSet e o Modelo de Maleta - O TClientDataSet e o dbExpress
Escrito por Carlos B. Feitoza Filho | |
Categoria: Artigos | |
Categoria Pai: Addicted 2 Delphi! | |
Acessos: 84471 |
Páginas dentro deste artigo
O TClientDataSet e o dbExpress
Quando combinado com o dbExpress (DBX), o TClientDataSet pode ser visto como um "cache" (ou maleta), no qual os cursores unidirecionais podem armazenar seus dados, assim os usuários podem visualizá-los como um conjunto de registros bidirecionais. Isto habilita capacidades SQL, pois é o DBX quem está fornecendo os dados ao TClientDataSet. Nós não precisamos mais nos preocupar com armazenamento local, apenas precisamos executar ApplyUpdates para enviar as alterações ao banco de dados no qual o DBX está conectado.
O que é o dbExpress?
O DBX é uma arquitetura de acesso a dados multi-plataformas, leve, rápida e aberta. Um driver DBX precisa implementar algumas interfaces para obter metadados, executar consultas SQL ou stored procedures, e retornar um cursor unidirecional. Voltaremos a falar disso em breve.
Personalizando o dbExpress
Como dito anteriormente o DBX foi criado como uma arquitetura de acesso a dados aberta, significando que qualquer um pode escrever um driver compatível (ou seria compilante?) com o DBX para uso com o Kylix, Delphi ou C++ Builder. Um artigo sobre o funcionamento intrínseco do DBX, escrito por Ramesh Theivendran, o arquiteto do DBX, foi publicado no site Borland Community. Apesar de este artigo ser apenas um esboço preliminar, ele deixou claro que qualquer um pode escrever um driver para o dbExpress.
Como um exemplo prático, a EasySoft desenvolveu o "dbExpress Gateway for ODBC", que pode ser usado para conectar ao ODBC do UNIX e, via "ponte ODBC-ODBC", ele também é capaz de conectar-se a um SQL Server remoto, a um banco de dados Access ou mesmo outros drivers ODBC do Windows.
Componentes
A paleta dbExpress contém sete componentes: TSQLConnection, TSQLDataSet, TSQLQuery, TSQLStoredProc, TSQLTable, TSQLMonitor e TSQLClientDataSet
TSQLConnection
O componente TSQLConnection é literalmente a conexão entre os drivers do DBX e os outros componentes da paleta DBX. Ele é o meio de ligação entre a aplicação e drivers que possibilitam o acesso aos bancos de dados. Se você soltar este componente em um TForm ou TDataModule, você verá apenas 12 propriedades e aquela que é provavelmente a mais usada é a propriedade ConnectionName, que pode ser atribuída a um dos valores da lista apresentada. Se você selecionar IBConnection então a propriedade DriverName terá o valor "INTERBASE", a propriedade GetDriverFunc terá o valor "getSQLDriverINTERBASE" a propriedade LibraryName terá o valor "dbxint30.dll" e a propriedade VendorLib terá o valor "gds32.dll". Tudo foi automaticamente selecionado baseado no valor IBConnection, selecionado em ConnectionName.
Você pode abrir a propriedade Params e editar os valores dos parâmetros. Estes são também automaticamente preenchidos, aliás, quando você seleciona um valor para a propriedade ConnectionName. Se você não quiser que isso aconteça, por exemplo, quando estiver escrevendo código não visual de acesso a banco de dados e você quiser informar seus próprios valores para os parâmetros, você pode configurar a propriedade LoadParamsOnConnection como false.
Se você der duplo clique no componente TSQLConnection, você verá as configurações de conexão para cada valor possível da propriedade ConnectionName.
Nesta tela também é possível alterar as configurações atuais e criar novas configurações, de forma que seja possível selecioná-la na propriedade ConnectionName.
Uma vez que tudo tenha sido configurado de forma adequada, você pode configurar a pripriedade Connected como true e, ou obter uma mensagem de erro de conexão, ou ver a propriedade simplesmente mudando para true, o que indicaria sucesso na conexão.
TSQLDataSet
Uma vez que o componente TSQLConnection esteja conectado, você poderá usar qualquer um dos outros componentes DBX disponíveis, como o TSQLDataSet, que é o mais "geral" destes componentes. Sempre comece por configurar a propriedade SQLConnection deste componente para um TSQLConnection disponível. Os componentes TSQLQuery, TSQLStoredProc e TSQLTable podem ser vistos como instâncias especiais de TSQLDataSet. De fato, isso me lembra muito o ADOExpress (dbGo), no qual o componente TADODataSet é o "pai" de TADOQuery, TADOStoredProc e TADOTable. Na verdade o núcleo dos componentes TxxxDataSet de ambos, dbGo e DBX, compartilham as propriedades CommandType e CommandText, com as quais você pode determinar o subtipo do componente. Se você configurar o valor de CommandType para ctQuery, então a propriedade CommandText é interpretada como uma consulta SQL. Se você configurar o valor de CommandType para ctStoredProc, então a propriedade CommandText especifica o nome de um stored procedure e, finalmente, se CommandType for configurado como ctTable, então CommandText deve conter o nome de uma tabela.
Em nosso caso, usando o componente geral TSQLDataSet, nós podemos configurar CommandType como ctTable e CommandText como "customer" e com isso selecionar a tabela "customer". Se você configurar a propriedade Active como true, você obterá os dados atuais em tempo de projeto e, se você configurar a propriedade LoginPrompt do componente TSQLConnection como false, você nem mesmo verá a tela de login. Esse é um comportamento padrão de todos os componentes de acesso a dados do Delphi. Nada de especial, nada diferente. Ainda...
Unidirecional!?
Agora nós podemos direcionar o foco para a paleta Data Controls e começar a usar um de seus componentes para exibir os dados que recebemos do TSQLDataSet ativo. Note que nós não podemos usar todos estes componentes agora sem algumas considerações especiais. Este é o ponto onde a maior diferença entre as arquiteturas do BDE e do DBX está presente. O componente TSQLDataSet e seus componentes derivados retornam um cursor unidirecional. Isso significa que você pode mover-se apenas para frente e nunca para trás e isso não é nada útil quando pretendemos usar, por exemplo, um TDBGrid (nós vemos apenas um registro de cada vez!) e muito menos quando usamos um TDBNavigator, porque clicando nos botões Back ou First irá invariavelmente levantar uma exceção!
Por que um cursor unidirecional? Bem, a resposta óbvia é velocidade. O BDE nunca foi nosso melhor amigo (podemos considerá-lo um bom amigo, ou um conhecido amigável), mas ele nos ajudou com necessidades simples e pequenas dos bancos de dados. Infelizmente o ovehead e os requisitos gerais de memória do BDE não são pequenos e as tabelas do BDE nunca foram conhecidos por suas "velocidades incríveis". Esta era uma área onde a Borland procurou mostrar algumas melhorias reais. A nova arquitetura chamada de dbExpress foi desenhada com isso em mente e, consequentemente, os cursores unidirecionais como Result Sets, sem o ovehead causado pelo gerenciamento de metadados ou armazenamento em cache dos dados.
Um cursor unidirecional é especialmente útil quando você apenas precisa ver os resultados uma vez, ou precisa percorrer os resultados do início ao fim (novamente, uma só vez), por exemplo, em um loop while not Eof, ao processar os resultados de uma query ou stored procedure. Situações do mundo real onde isso é útil incluem a geração de relatórios e aplicações para servidores web que produzem dinamicamente páginas html como resultado.
Mas especialmente quando combinado com componentes visuais de acesso a dados, você rapidamente percebe que em ambientes gráficos os usuários certamente vão querer navegar em qualquer direção dentro de Result Sets. Então, você precisa, de alguma forma, colocar os registros em cache a fim de permitir sua exibição em um TDBGrid e também a navegação em qualquer direção. É aí onde o TClientDataSet entra em ação. É totalmente possível e pertinente usar um TDataSetProvider ligado ao TSQLDataSet e então usar um TClientDataSet para obter os dados do TDataSetProvider. O resultado é um TClientDataSet que obtém registros (uma vez) de uma fonte unidirecional, o TSQLDataSet. O TDataSetProvider é usado apenas como um meio local de transporte de dados.
Migração de tecnologia
O fato de o TClientDataSet estar disponível tanto no Delphi quanto no Kylix significa que existe uma forma simples e rápida de migrar tabelas de bancos de dados locais, como tabelas em Paradox ou dBASE. Este é o primeiro passo a ser feito para migrar do BDE para o DBX: migração de dados. O segundo passo é migrar a aplicação em si.
Para realizar o primeiro passo devemos migrar os dados do BDE para o formato nativo do TClientDataSet. Considere o código abaixo de uma aplicação chamada dbAlias que eu escrevi. Esta aplicação vai converter todas as tabelas acessadas por um alias (passado via parâmetro) para arquivos .cds:
{$APPTYPE CONSOLE}
program dbAlias;
uses
Classes, SysUtils, DB, DBTables, Provider, DBClient;
var
i: Integer;
TableNames: TStringList;
Table: TTable;
DataSetProvider: TDataSetProvider;
ClientDataSet: TClientDataSet;
begin
TableNames := TStringList.Create;
with TSession.Create(nil) do
try
AutoSessionName := True;
GetTableNames(ParamStr(1), '', True, False, TableNames);
finally
Free
end {TSession};
Table := TTable.Create(nil);
DataSetProvider := TDataSetProvider.Create(nil);
ClientDataSet := TClientDataSet.Create(nil);
try
Table.DatabaseName := ParamStr(1);
for i : =0 to Pred(TableNames.Count) do
begin
writeln(Table.TableName);
Table.TableName := TableNames[i];
Table.Open;
DataSetProvider.DataSet := Table;
ClientDataSet.SetProvider(DataSetProvider);
ClientDataSet.Open;
ClientDataSet.SaveToFile(ChangeFileExt(Table.TableName,'.cds'));
ClientDataSet.Close;
Table.Close
end
finally
Table.Free
end
end.
Esta é uma forma rápida e grosseira de converter[1] suas tabelas Paradox e dBASE referenciadas por um alias do BDE para arquivos cds, os quais podem ser carregados novamente por componentes TClientDataSet em uma outra aplicação. O uso que você fará desses dados depende obviamente do seu sistema.
Cursores unidirecionais e conjuntos de dados somente leitura?
Tanto o Delphi 6 como o Delphi 7 e o Kylix usam a nova camada de acesso a dados independente de plataforma chamada de dbExpress, mas como tirar o máximo proveito dessa nova tecnologia?
Vou mostrar agora que, ao usar o dbExpress, nós precisamos adotar uma nova forma de olhar os dados (especialmente na hora de salvá-los), porque o DBX dispõe apenas de datasets somente leitura com cursores unidirecionais, por isso, não há como confirmar nossas alterações automaticamente mediante um simples post em seus componentes.
Para ilustrar este ponto, e mostrar como proceder de forma correta, vamos construir uma aplicação usando o DBX. Primeiro inicialize um novo projeto Win32 de forma usual. Solte um componente TSQLConnection no TForm disponível e configure sua propriedade ConnectionName para IBCONNECTION (ou a de sua preferência). Você pode dar duplo clique no TSQLConnection para iniciar o editor de conexões e garantir que todas as propriedades do ConnectionName escolhido estejam corretas. Em seguida solte um componente TSQLTable e configure sua propriedade SQLConnection como SQLConnection1, que deve ser o nome do TSQLConnection. Selecione uma das tabelas disponíveis na propriedade TableName. Neste ponto nós temos um dataset somente leitura e unidirecional (o qual suporta movimentação de registros apenas um passo de cada vez para frente ou de volta ao início, mas nenhuma outra operação). Esse comportamento é excelente caso se pretenda usar um componente TDataSetTableProducer, onde precisamos andar para frente no resultset apenas uma vez, mas não é nada útil em outras situações.
TClientDataSet
Para permitir a exibição das informações contidas no TSQLTable (ou qualquer dataset DBX) precisamos armazená-los dentro de um TClientDataSet usando um TDataSetProvider como "conector". Então, solte no TForm um componente TDataSetProvider e um TClientDataSet, os quais se encontram na paleta Data Access. Atribua o componente TSQLtable à propridade DataSet do TDataSetProvider e então atribua o nome do TDataSetProvider à propriedade ProviderName do TClientDataSet. No momento em que você abrir o TClientDataSet (por exemplo, configurando sua propriedade Active como True) o conteúdo de TSQLTable será percorrido (apenas uma vez) e os registros serão providos ao TClientDataSet, o qual os armazenará daquele momento em diante. Nós podemos agora usar um TDataSource e (por exemplo) um TDBGrid para exibir o conteúdo disponível no TClientDataSet.
Manipulação de dados
O problema da manipulação de dados ocorre quando executamos a aplicação, fazemos algumas alterações em alguns campos e registros e saímos da mesma. Quando abrimos a aplicação novamente nós vemos apenas os valores antigos! O TClientDataSet é perfeito para colocar dados em cache, mas não é capaz de atualizar[2] o banco de dados para nós de forma automática.
Como o componente TSQLTable é somente leitura e não é capaz de manipular de forma tradicional um banco de dados (usando métodos insert, edit, delete e post), precisamos usar o TClientDataSet, o qual pode usar o TDataSetProvider para realizar estas operações diretamente. O TDataSetProvider, é capaz de gerar os comandos SQL de inserção, atualização e exclusão e enviá-los diretamente ao banco de dados, sem usar o métodos de alto nível, indisponíveis nos componentes do DBX.
Então, para que as operações de banco de dados sejam executadas, precisamos executar o método ApplyUpdates do TClientDataSet. Podemos colocar no TForm um TButton, configurar sua propriedade Caption para "Aplicar Alterações" e, dentro do manipulador do evento OnClick desse botão devemos escrever apenas uma linha de código:
procedure TForm1.Button1Click(Sender: TObject);
begin
ClientDataSet1.ApplyUpdates(0)
end;
Este simples comando vai enviar todas as atualizações pendentes no TClientDataSet para o banco de dados acessado pelo DBX. Caso um erro aconteça no momento da execução dos comandos -- por exemplo, quando um registro alterado por nós já tiver sido alterado por outro usuário --, um erro conhecido como Reconcile (EReconcileError) será gerado e poderá ser manipulado no evento OnReconcileError do TClientDataSet. Neste evento o usuário poderá decidir como o erro será corrigido. Maiores detalhes de como manipular erros EReconcileError serão dados mais adiante na seção sobre DataSnap deste mesmo artigo.
Automatizando o ApplyUpdates
Apesar da solução apresentada funcionar, você não pode realisticamente esperar que seus clientes e usuários finais se lembrem de clicar no botão "Aplicar Alterações" quando eles quiserem simplesmente salvar seus trabalhos. Nós podemos facilitar a vida dos nossos usuários executando o método ApplyUpdates de forma automática no evento AfterPost do TClientDataSet:
procedure TForm1.ClientDataSet1AfterPost(DataSet: TDataSet);
begin
(DataSet as TClientDataSet).ApplyUpdates(0)
end;
Apesar de você estar se sentindo bem com essa solução, ainda existem situações que precisamos considerar, por exemplo, ao excluir um registro não existe método Post, logo, o evento AfterPost não vai funcionar neste caso e por isso precisamos colocar também o mesmo código no evento AfterDelete.
Finalmente, quando você fecha sua aplicação imediatamente após sua última alteração, mas antes de executar um Post, por exemplo, se você alterou o valor em um TDBEdit, mas não confirmou sua ação (Post), então você pode querer que esta alteração seja confirmada também. Isso significa que você precisa executar novamente a mesma linha de código descrita anteriormente, nos eventos OnDestroy ou OnClose de seu TForm, TDataModule ou TFrame (ou qualquer outros contêiner que você esteja usando para seu TClientDataSet)