-
CURSO POR GUINTER PAULI CLUBE DELPHI
Curso de dbExpress e DataSnap
Parte I - Apresentao
A idia de construir este curso partiu na boa aceitao que tivemos
em lanar o curso em 20 partes sobre acesso a dados no Delphi for
.NET, com ADO.NET (Active Data Objects) e BDP (Borland Data
Provider), que o leitor pde acompanhar nos ltimos meses aqui na
ClubeDelphi. O curso tambm est disponvel na forma de E-Book. A
partir deste artigo, damos incio um curso de acesso a dados usando
o Delphi Win32, voltado para o ambiente client/server e multitier.
Acredito que muitos de vocs desenvolvedores j possuem pelo menos
uma aplicao desse tipo em produo. Que tal conhecer alguns recursos
interessantes para aplicar rapidamente em suas aplicaes, fazendo
otimizaes e garantindo performance? Este o primeiro de uma srie de
30 artigos que mostrar tcnicas avanadas de uso das principais
tecnologias de acesso a dados do Delphi Win32: dbExpress e
DataSnap. Ao longo deste curso, voc conhecer importantes
caractersticas e recursos de cada um dos componentes utilizados. Em
especial, pretendo apresentar inmeras dicas e segredos de utilizao
de um componente que considero ser o melhor j criado pela Borland:
o ClientDataSet. Pretendo criar um curso bastante dinmico: gostaria
de elaborar tpicos de acordo com a necessidade e idias de vocs,
amigos leitores. Escrevam e sugiram tpicos que gostariam que fossem
contemplados aqui no curso. Alm disso, no final, pretendo colocar
um grande documento de FAQs com perguntas e respostas, enviadas
pelos leitores que faro o curso. Para criar os exemplos aqui
demonstrados, voc pode utilizar o Delphi 6, 7 ou o Delphi 2005.
Como banco de dados, utilizarei o Interbase 7.5, mas voc pode
utilizar qualquer outro banco de sua preferncia, como Firebird, SQL
Server, Oracle, MySQL, todos os cdigos apresentados funcionam
perfeitamente com qualquer um desses SGDBs. Desejo um bom curso a
todos, sugestes sero bem-vindas. Um abrao a todos e desejo sucesso
nos projetos de banco de dados com Delphi DownloadVoc pode fazer
download de todos os exemplos deste curso a partir do endereo
cc.borland.com/cc/ccweb.exe/author?authorid=222668 dbExpress,
DataSnap e ClientDataSet: tcnicas avanadas Para mais informaes
sobre acesso a dados no Delphi e tcnicas avanadas, sugiro a leitura
do meu livro, Delphi: Programao para Banco de Dados e Web, como
apoio para o aprendizado das tecnologias. Na obra mostro vrias
tcnicas introdutrios e avanadas de desenvolvimento com
ClientDataSet, dbExpress e DataSnap (multicamadas, incluindo SOAP e
COM+).
Parte II - Conhecendo os componentes
Nesta primeira parte do curso de dbExpress, vamos conhecer os
principais componentes da paleta dbExpress e DataSnap do Delphi,
vendo suas funcionalidades e objetivos.
Componentes do dbExpress e DataSnap
Os componentes dbExpress e DataSnap podem ser vistos na figura a
seguir:
-
Nesta primeira parte do curso, vamos conhecer brevemente cada um
dos componentes envolvidos em aplicaes dbExpress. Nos artigos
seguintes, vamos detalhar cada um deles.
Viso geral dos componentes
SQLConnection Esse componente responsvel pela conexo com o banco
de dados.
TSQLDataset Componente responsvel por obter dados de um servidor
SQL usando cursores unidirecionais. Tambm pode executar uma
procedure no servidor. Ele pode atuar tanto como uma Query, uma
Table ou uma StoredProc.
TSQLQuery Componente que fornece uma maneira de executar um
comando SQL usando uma conexo dbExpress.
TSQLStoredProc Usado para executar um procedure remoto no
servidor SQL.
TSQLTable Usado para representar uma tabela acessada atravs de
uma conexo dbExpress.
TSQLMonitor Monitora as trocas de mensagens e instrues SQL
feitas entre uma aplicao cliente e um servidor SQL.
TClientDataset Utilize TClientDataset para fornecer um mecanismo
de cache para os Datasets unidirecionais. Por ser conectado a um
TDatasetProvider, os dados podero ser capturados de um servidor de
aplicao.
TDatasetProvider
-
TDatatasetProvider prov dados de um Dataset e aplica as
atualizaes feitas em um TClientDataset (delta) no servidor de
dados. Ele responsvel por criar os pacotes de dados que trafegam
entre uma aplicao cliente e um servidor de aplicao em uma
arquitetura multicamadas. Ele pode se comunicar com um servidor de
aplicao por meio da interface IAppServer.
SimpleDataSet Esse componente o conjunto de quatro componentes,
e facilita a conexo rpida com banco de dados, indicado para criao
de aplicaes simples e prottipos.
DCOMConnection Efetua uma conexo com um servidor de aplicao
DataSnap, do tipo DCOM, MTS ou COM+
SocketConnection Efetua uma conexo com um servidor de aplicao
DataSnap, do tipo Sockets
WebConnection Efetua uma conexo com um servidor de aplicao
DataSnap, usando o protocolo HTTP. Seu uso no mais aconselhado,
sendo prefervel o uso de um SOAPConnection para conexes DataSnap
atravs da Web / HTTP.
SimpleObjectBroker Permite criar um mecanismo simples de
balanceamento de carga em servidores DataSnap. Por exemplo, ele
pode despachar uma conexo cliente para um segundo servidor de
aplicao se o primeiro servidor estiver congestionado.
SharedConnection Permite acesso a um DataModule filho em um
servidor de aplicao com mltiplos mdulos.
LocalConnection Permite simular um ambiente multicamadas em um
ambiente 2-tier, atravs de um mdulo compartilhado (DataModule). Com
isso, ClientDataSets podem enxergar Providers em outras units, como
se fosse uma camada fsica.
ConnectionBroker Este componente tem por finalidade abstrair
(isolar) o tipo de conexo para os ClientDatasets.
-
Se algum dia for preciso mudar o tipo de servidor, no seria
necessrio reconfigurar os ClientDatasets caso se mudasse
DCOMConnection para SOAPConnection, por exemplo.
Parte III - Arquitetura
Nesta parte do curso, vamos examinar detalhes sobre a
arquitetura de aplicaes dbExpress que utilizam os componentes
bsicos, tpicos de uma aplicao client/server ou multicamadas:
SQLConnection, SQLQuery, DataSetProvider e ClientDataSet . Veremos
tambm as vantagens em se utilizar uma aplicao multicamadas.
Arquitetura
A figura a seguir mostra as partes envolvidas em um sistema
multicamadas. Na primeira camada, temos o chamado servidor de
dados. Nele est localizado o banco de dados propriamente dito. O
banco de dados pode ser Oracle, DB2, MySQL, Interbase ou qualquer
outro suportado pelo dbExpress. Como a conexo feita via TCP/IP, voc
pode usar um SO diferente do Windows, como Linux. A camada
intermediria, conhecida como servidor de aplicao, contm o
DataModule (tambm conhecido como RemoteDataModule ) responsvel pelo
acesso a dados. Nessa camada se encontram os componentes dbExpress,
como SQLConnection, SQLQuery, SQLDataSet e os DataSetProviders .
Nessa camada voc tambm vai incluir as chamadas regras de negcio .
Business Rules so regras impostas sobre dados, que no sero mais
processadas no servidor SQL. Com isso, seu sistema consegue
centralizar as regras sem depender do tipo de banco utilizado. E
finalmente, a camada cliente responsvel apenas pela apresentao dos
dados. Contm basicamente regras de tratamento de tela e interface.
Por isso, so chamadas de thin-clients (clientes leves).
A seguir, vamos examinar as vantagens de se desenvolver aplicaes
multicamadas com DataSnap e dbExpress. Vantagens de uma soluo
Multitier
Modularizao da aplicao em 3 camadas
Quando desenvolvemos uma aplicao Multicamadas com DataSnap,
separamos a lgica de negcio e regras de acesso a banco de dados das
regras de interface de usurio. Dessa forma, vrios clientes podem
compartilhar as mesmas regras, que ficam encapsuladas em uma camada
de acesso comum. Observe que em uma aplicao 2 camadas h geralmente
uma redundncia de regra de negcio nas estaes clientes, e uma
simples mudana nessa regra requer uma nova distribuio da aplicao.
Suponha que voc esteja fazendo um simples cadastro de clientes, com
interface tradicional baseada em formulrios. Ento voc inclui no
-
DataModule da aplicao uma determinada validao de dados. Se voc
agora precisar construir uma verso On-Line deste cadastro, atravs
de um formulrio que poder ser aberto em um browser Web, ento ser
necessrio recodificar a regra anterior. Neste caso, uma camada
lgica intermediria poderia centralizar todas as regras e atender
tanto aplicaes clientes baseadas em formulrios, quanto aplicaes
baseadas em browser.
Clientes Leves (Thin-Clients)
Uma aplicao cliente de uma arquitetura Multitier basicamente
contm cdigo de interface. Todo o processamento das solicitaes de
dados ao servidor SQL so feitas na camada intermediria, de forma
que um cliente nunca se comunica diretamente com o banco de dados.
Dessa forma, uma aplicao cliente DataSnap muito leve, pequena, e de
configurao quase zero. Por este motivo este tipo de cliente tambm
conhecido como Thin-Client ou cliente leve (magro).
Economia de licenas de acesso a Banco de dados
Quando trabalhamos com aplicaes 2 camadas, cada cliente de nossa
aplicao se comunica diretamente com o banco de dados, por meio de
um SQL Client. Este SQL Client um conjunto de bibliotecas (que
muitas vezes ocupam vrios MB de espao em disco) que possibilita um
terminal da rede trocar comandos SQL com um servidor de dados SQL.
Voc precisa instalar estas bibliotecas em cada estao que acesse o
servidor. Quando o driver do cliente troca de verso, voc precisa
atualizar todas as mquinas da rede (isso sem falar da sua aplicao).
Um outro aspecto nada interessante que geralmente as empresas
fabricantes de banco de dados cobram licenas extras pela instalao
destas bibliotecas clientes. Agora se voc construir seu sistema
baseado em uma arquitetura Multitier, as bibliotecas do SQL Client
(seja qual for o banco) devero ser instaladas somente na camada
intermediria (camada de acesso a dados), poupando conexes no
servidor e economizando licenas. Todos os clientes dessa forma
compartilham uma nica conexo com o banco de dados SQL. Se seu
aplicativo utiliza ainda o BDE como tecnologia Borland de acesso a
dados, estes drivers tambm sero necessrio somente na aplicao
intermediria, e no mais no cliente final. O mesmo vale para os
drivers de acesso a dados do dbExpress.
Escalabilidade
Um software bem desenvolvido sobre uma arquitetura Multicamadas
extremamente escalvel. Isto , a medida que mais clientes comeam a
conectar no servidor de aplicao e utilizar seus recursos, no h uma
perda de desempenho e performance, como acontece em aplicaes 2
camadas tradicionais.
Independncia de Linguagem
Uma camada de negcio construda sobre um padro COM, por exemplo,
pode ser acessada por clientes escritos em diversas linguagens de
programao que dem suporte ao COM. Essa comunicao possvel devido ao
mecanismo conhecido como Interfaces. Um servidor COM (escrito em
Delphi, por exemplo) pode ser acessado de um cliente escrito em VB,
ASP, C, etc.
Independncia de Banco de Dados
Como uma aplicao cliente Datasnap no se comunica diretamente com
o banco de dados, este no precisa nem mesmo saber qual o banco de
dados utilizado. possvel migrar de Banco de Dados, por exemplo, de
Interbase para Oracle, sem mesmo recompilar as aplicaes clientes (e
as vezes nem mesmo a camada intermediria).
Balanceamento de Carga
Um servidor de aplicao pode ser configurado para automaticamente
distribuir as conexes clientes para outros servidores de aplicao,
tornando a arquitetura ainda mais escalvel. Isso facilmente
alcanado apenas se usando o componente SimpleObjectBroker.
Funcionamento de uma aplicao dbExpress
A figura a seguir mostra uma aplicao tpica dbExpress, e todos os
passos envolvidos desde a requisio de dados at a atualizaes das
alteraes no servidor. Todos os procedimentos a
-
seguir acontecem imediatamente aps voc dar um Open em um
ClientDataSet . Inclu tambm a especificao das etapas de atualizao.
Conexo: o componente SQLConnection estabelece uma conexo TCP/IP com
o servidor SQL; Abertura de um cursor de dados: um SQLDataSet ou
SQLQuery efetua uma consulta ( select ) no banco de dados, por meio
de um SQLConnection . O servidor SQL abre ento um cursor de dados,
alocando recursos; DataSetProvider : aps o cursor ser aberto, o
DataSetProvider varre o cursor de dados (uma operao while not eof )
montando um pacote de dados ( DataPacket ). Providing : o
DataSetProvider fecha o cursor de dados, liberando recursos, e
envia os dados obtidos em forma de DataPacket para o ClientDataSet
, em uma operao chamada Providing . Cache : o ClientDataSet
armazena os dados em cache, local, no sendo necessria nenhuma
conexo com o banco de dados; ApplyUpdates - o ClientDataSet envia
as atualizaes de volta ao DataSetProvider , em um pacote de dados
conhecido como Delta ; Resolving para cada registro alterado, o
DataSetProvider tenta atualizar o respectivo registro no banco de
dados, gerando automaticamente instrues de atualizao como update,
delete e insert . Reconcile eventuais erros ocorridos durante o
Resolving so devolvidos ao ClientDataSet , para que sejam tomadas
solues, em um processo chamado Reconcile .
-
Parte IV SQLConnection criando conexes
Nesta parte do curso, vamos conhecer em detalhes o componente
SQLConnection e criar nossa primeira conexo dbExpress.
A classe TSQLConnection
A classe TSQLConnection representa uma conexo dbExpress. Um
SQLConnection faz uso de um driver para conectar um servidor de
banco de dados. Os atuais bancos suportados so:
DB2 MySQL SQL Server Informix Oracle InterBase
O dbExpress no suporta bancos locais, como Paradox e DBase. Isso
porque o dbExpress nada mais que uma casca fina sobre a API do
banco de dados SQL. Como Paradox e DBase no so servidores SQL
(SDBDs), no so suportados pelo dbExpress.Os driver e conexes so
definidos em dois arquivos de configurao. O primeiro,
dbxdrivers.ini em Windows ou os dbxdrivers em Linux, lista os
drivers instalados e as bibliotecas (DLLs ou shared objects no
Linux (so)) requeridos pela conexo. O segundo, dbxconnections.ini,
em Windows ou os dbxconnections em Linux, lista as configuraes da
conexo. Cada configurao representa um conjunto de parmetros de
componentes TSQLConnection e descreve uma conexo com um banco de
dados. Para criar uma configurao, basta dar um duplo clique em um
componente TSQLConnection e usar editor para criar a conexo.
SQLConnection - Principais Propriedades
-
ActiveStatements Nmero de comandos ativos sendo executados no
banco de dados AutoClone Especifica se o componente automaticamente
clona conexes com o banco de dados quando for necessrio Connected
Indica se a conexo est ativa ConnectionName Nome da conexo no
arquivo de configurao ConnectionState Indica o corrente estado da
conexo DataSets Lista todos os DataSets ativos da conexo DriverName
Indica o nome do driver associado conexo GetDriverFunc Indica a
funo exportada na DLL do driver InTransaction Se uma transao est em
progresso KeepConnection Indica se a conexo deve ficar ativa se no
existirem DataSets abertos LibraryName Nome da DLL do driver
dbExpress LoadParamsOnConnect Indica se o componente deve ler as
configuraes dinamicamente a partir do dbxconnections.ini LocaleCode
Indica se a ordenao em DataSets deve ser com base na localizao
MaxStmtsPerConn Indica o nmero mximo de comandos ativos por conexo
MetaData Permite acesso aos metadados do banco de dados
MultipleTransactionsSupported Indica se o banco de dados suporta
mltiplas transaes Params Lista os parmetros de conexo ParamsLoaded
Indica se os parmetros foram lidos do dbxconnections.ini
-
SQLConnection Acesso ao objeto interno do driver dbExpress que
representa a conexo SQLHourGlass Indica se o cursor de tela (SQL)
deve ser usado durante processamentos TableScope Indica quais tipos
de tabelas devem ser retornadas em operaes de metadados
TransactionsSupported Indica se o banco de dados suporta transaes
VendorLib Indica o nome da biblioteca cliente (DLL) do banco de
dados
SQLConnection - Principais Mtodos
CloneConnection Retorna uma cpia do objeto de conexo
CloseDataSets Fecha todos os DataSets associados conexo Commit
Efetua um Commit em uma transao Create Cria uma instncia da classe
TSQLConnection Destroy Destri a instncia do objeto Execute Executa
um comando SQL no banco de dados ExecuteDirect Executa um comando
SQL no banco de dados, que no possua parmetros GetDefaultSchemaName
Retorna o default schema do objeto do banco de dados GetFieldNames
Obtm a lista de campos de uma tabela, em um TStrings GetIndexNames
Obtm a lista de ndices de uma tabela, em um TStrings
GetLoginUsername Retorna o nome do usurio logado no BD
GetPackageNames Obtm a lista de packages do BD, em um TStrings
GetProcedureNames
-
Obtm a lista de stored procedures do BD, em um TStrings
GetProcedureParams Obtm a lista de parmetros de stored procedures
do BD, em um TStrings GetSchemaNames Obtm o nome de todos os
objetos do BD, em um TStrings GetTableNames Obtm a lista de tabelas
do BD, em um TStrings LoadParamsFromIniFile Carrega os parmetros de
conexo a partir de um arquivo INI Rollback Efetua um RollBack em
uma transao SetTraceCallbackEvent Define uma funo de callback que
chamada para cada comando executado no servidor StartTransaction
Inicia uma transao Close Fecha a conexo Open Abre a conexo
SQLConnection - Principais Eventos
OnLogin Permite definir parmetros de login (username e senha).
TraceCallbackEvent Permite acesso a uma funo de callback que
chamada para cada comando executado no servidor AfterConnect
Disparado aps a conexo ser estabelecida BeforeConnect Disparado
antes da conexo ser estabelecida AfterDisconnect Disparado aps a
conexo ser fechada BeforeDisconnectDisparado antes da conexo ser
fechada
Criando uma conexo
muito simples criar uma conexo dbExpress. Pressupondo que voc j
tenha o Interbase rodando e previamente configurando (neste curso
vou usar o IB 7.5), basta colocar um SQLConnection no form, dar um
duplo clique sobre ele e acessar o editor de conexes:
-
No editor de conexes do dbExpress, clique no cone +. Escolha o
driver do banco desejado e d um nome para a conexo:
Informe no parmetro DataBase o caminho do banco de dados:
Nota: O parmetro Database utilizado de diferentes formas,
dependendo do driver. Para conexes IB / FB, preciso especificar o
nome do host (servidor) juntamente com o caminho do banco,
separados por :. errado tentar acessar o servidor usando algo do
tipo \\servidor\c$\caminho\db.gdb. Nesse caso, o servidor seria a
mquina local, mas acessando um arquivo em outra mquina usando
compartilhamento de rede. Para o SQL Server, por exemplo, o nome do
servidor e do banco so indicados em parmetros distintos (HostName e
Database). Nesse caso, Database no o caminho fsico do banco no
disco, mas o nome registrado no Enterprise Manager. Para o DB2, o
Database deve indicar o Alias configurado no DB2 Client, que contm
outras informaes de acesso, como o endereo do servidor, porta etc.
Procedimento semelhante tambm feito para o Oracle.Essa configurao
fica salva no arquivo dbxconnections.ini
-
dbxconnections.ini
O dbExpress e o BDE so bastante semelhantes com relao aos
parmetros utilizados em uma conexo. Como voc deve lembrar, o BDE
guardava informaes sobre os aliases em um arquivo chamado
idapi32.cfg, localizado normalmente em c:\Arquivos de
Programas\Arquivos Comuns\Borland Shared\BDE. Esse arquivo no era
manipulado diretamente, e sim utilizando-se o BDE Administrator a
partir do Painel de Controle. Com isso, ficava muito simples
alterar dinamicamente um parmetro de conexo, como o caminho do
banco ou endereo do servidor, pois essas informaes ficavam externas
aplicao. No era preciso recompilar nada caso fosse necessrio fazer
alguma modificao nos parmetros de acesso. Tambm era possvel
utilizar um componente Database. Em compensao, o BDE uma camada
pesada de acesso a servidores SQL. Foi feito para o mundo duas
camadas e possui um mecanismo de cache rudimentar. Freqentemente o
desenvolvedor sentia-se obrigado a instalar quase 18 MB de DLLs
para acesso a um BD. Ao contrrio, o dbExpress um engine leve de
acesso, baseado na implementao de interfaces que acessam
diretamente o driver cliente do BD, dispensado a instalao de
bibliotecas adicionais. Tudo o que voc precisa distribuir a DLL
indicada na propriedade LibraryName do SQLConnection, cujo tamanho
varia entre 90 kb e 200 kb, dependendo do driver. Tambm necessrio
distribuir a biblioteca Midas.dll, que na atual verso possui
somente cerca de 290 Kb. Semelhante ao BDE, as informaes sobre as
conexes criadas no dbExpress ficam em um arquivo de configurao,
chamado dbxconnections.ini, localizado normalmente em c:\Arquivos
de Programas\Arquivos Comuns\Borland Shared\dbExpress. Abra esse
arquivo e veja que ele contm uma sesso chamada DB_IB, que define os
parmetros da conexo que criamos no Delphi. Veja um trecho do
arquivo a seguir (esses parmetros so os mesmos que esto atualmente
na propriedade Params do SQLConnection):
[DB_IB]DriverName=InterbaseDatabase=localhost:c:\caminho\db.gdbUser_Name=sysdbaPassword=masterkeySQLDialect=3...
O arquivo dbxconnections.ini utilizado, at agora, apenas pela IDE
do Delphi. Repare que cada conexo tem um parmetro chamado
DriverName. O Delphi utiliza esse valor para configurar algumas
propriedades do SQLConnection que so especficas do driver
utilizado. Por exemplo, se o DriverName for Interbase, as
propriedades GetDriverFunc, LibraryName e VendorLib tero os valores
getSQLDriverINTERBASE, dbexpint.dll e gds32.dll, respectivamente.
Esses valores mudam para o DB2, Oracle, SQL Server etc. Essas
configuraes so obtidas atravs de um segundo arquivo de configurao,
chamado dbxdrivers.ini. Dica: Para mais informaes sobre os arquivos
de inicializao do dbExpress, consulte os tpicos dbxconnections.ini
e dbxdrivers.ini na ajuda do Delphi.
Testando a conexo
Pronto, agora j podemos testar a conexo! Para isso, coloquei um
Button no form e digitei: procedure TForm1.Button1Click(Sender:
TObject);begin try try SQLConnection1.Open();
-
ShowMessage('Conexo feita com sucesso!'); except
ShowMessage('Erro ao conectar'); end; finally if
SQLConnection1.Connected then SQLConnection1.Close(); end;end;
Lembre-se de definir o LoginPrompt como False. Execute e teste a
aplicao.
Parte V Introduo ao uso de DataSets Unidirecionais
Neste artigo teremos uma pequena introduo ao conceito e uso de
DataSets Unidirecionais do dbExpress, atravs dos componentes
TSQLDataSet, TSQLTable e TSQLQuery.
O que so DataSets unidirecionais
A principal funo dos Datasets unidirecionais recuperar dados de
um banco SQL. Eles no mantm nenhum buffer na memria ou criam
qualquer tipo de cache, o que era comum em Datasets bidirecionais,
como os usados no BDE. Por serem implementados desta forma,
Datasets unidirecionais so bem mais rpidos, exigem pouco
processamento e utilizam o mnimo de recursos da mquina,
diferentemente dos Datasets TQuery e TTable baseados no BDE. Em
Datasets unidirecionais no permitido: edio, campos lookup, filtros
e navegao com prior e last. Ao tentar realizar operaes no
permitidas sobre um Dataset unidirecional, uma exceo do tipo
EdataBaseError levantada com a mensagem : Operation not allowed on
a unidirectional dataset. At o BDE, quando o usurio rolava o cursor
de dados na aplicao cliente, por exemplo, usando um DBNavigator,
havia uma troca de mensagens com o kernel do BDE, que precisava
manter o cursor alocado no servidor de banco de dados para permitir
a navegao bidirecional. Isso poderia comprometer a performance de
solues baseadas nessa arquitetura, visto que para atender n cliente
simultaneamente, o BD precisava manter ativo os cursores alocados
no servidor. Com o dbExpress, o tempo de transao e vida til do
cursor de dados no servidor bastante
-
pequeno, devido ao uso de DataSetProviders e ClientDataSets.
Quando damos um Open em um ClientDataSet, enviada uma solicitao
para o DataSetProvider, que abre o Dataset unidirecional associado.
O DataSetProvider varre ento o cursor aberto pela consulta e
empacota os dados em um DataPacket, que alocado na memria do
ClientDataSet. Nesse momento, o usurio pode trabalhar
tranquilamente nos dados em tela, e o dbExpress pode fechar a
consulta (cursor) que no ficar mais ativa (diferente do BDE). Por
esse motivo, aplicaes dbExpress e DataSnap so extremamente
rpidas.
DataSets do dbExpress
Os componentes da guia dbExpress possuem os mesmos ancestrais
dos componentes baseados no BDE. Isso significa que muita coisa ser
semelhante ao migrar de BDE para DBX, graas aos recursos de abstrao
e polimorfismo, principalmente das classes TDataset, TField e
TCustomConnection. Essas classes formam a base para os vrios
componentes de acesso a dados, campos e conexo a bancos de dados
disponveis no Delphi. Os DataSets do pacote dbExpress so chamados
unidirecionais. Basicamente, este tipo de Dataset tem a funo de
retornar dados de um servidor SQL, mas no de manipul-los
(buffering). Para cada banco de dados que vamos acessar, o
dbExpress fornece um driver especfico que deve ser distribudo
juntamente com a aplicao. Todos os TDatsets usados no dbExpress
herdam de TCustomSQLDataset. Todas as classes do dbExpress esto
declaradas na unit SqlExpr.pas.
Exemplo usando somente DataSets Unidirecionais
possvel utilizar DataSets Unidirecionais diretamente, sem usar
um ClientDataSet. Essa tcnica bastante utilizada, por exemplo, para
confeco de relatrios. Ou seja, lemos um registro, fazemos alguma
coisa com ele, e navegamos para o prximo, sem armazenar nada em
memria. exatamente isso que mostrarei neste exemplo. Inicie uma
nova aplicao VCL no Delphi.
-
Coloque um SQLConnection e um SQLDataSet no formulrio.
No SQLConnection, configure uma conexo para o banco Employee do
Interbase ou do Firebird (j discutimos conexes anteriormente, de
forma que no vou entrar em detalhes aqui).
-
Aponte o SQLDataSet para o SQLConnection, atravs da propriedade
Connection e em CommandText digite select * from customer
Coloque um DataSource e aponte sua propriedade DataSet para o
SQLDataSet. Neste momento, voc dever receber a seguinte mensagem de
erro:
-
Isso acontece pois um DBGrid exige navegao bidirecional no
cursor de dados, o que no suportado pelo dbExpress. Para isso, voc
precisaria de um ClientDataSet (no usaremos ainda neste exemplo).
Retire ento o DBGrid e coloque um ListView. No evento OnShow do
formulrio digite: procedure TForm1.FormShow(Sender: TObject);var
it: TListItem; i: integer;begin ListView1.ViewStyle := vsReport;
SQLDataSet1.Open; try for i := 0 to pred(SQLDataSet1.Fields.Count)
do with ListView1.Columns.Add do Caption :=
SQLDataSet1.Fields[i].FieldName; while not SQLDataSet1.Eof do begin
it := ListView1.Items.Add; it.Caption :=
SQLDataSet1.Fields[0].AsString; for i := 1 to
pred(SQLDataSet1.Fields.Count) do
it.SubItems.Append(SQLDataSet1.Fields[i].AsString);
SQLDataSet1.Next; end; finally SQLDataSet1.Close; end;end; Aqui no
estamos usando nenhuma espcie de cache, fazendo uma navegao
otimizada e unidirecional. Para cada registro do SQLDataSet,
adicionamos os valores dos campos no ListView e a seguir navegamos
para o prximo registro. A figura a seguir mostra o exemplo em
execuo:
-
ISQLCursor
Quando usamos DataSets do dbExpress diretamente, na verdade
estamos trabalhando diretamente com o driver a ele associado. Essa
interface dbExpress com o banco de dados feito atravs de drivers e
interfaces, definidas na unit DBXpress.pas. Veja a seguir a definio
da interface responsvel pela manipulao de cursores (se voc
observar, ver que usamos alguns mtodos dessa interface no exemplo
anterior): ISQLCursor = interface function SetOption(eOption:
TSQLCursorOption; PropValue: LongInt): SQLResult; stdcall; function
GetOption(eOption: TSQLCursorOption; PropValue: Pointer; MaxLength:
SmallInt; out Length: SmallInt): SQLResult; stdcall; function
getErrorMessage(Error: PChar): SQLResult; overload; stdcall;
function getErrorMessageLen(out ErrorLen: SmallInt): SQLResult;
stdcall; function getColumnCount(var pColumns: Word): SQLResult;
stdcall; function getColumnNameLength( ColumnNumber: Word; var
pLen: Word): SQLResult; stdcall; function
getColumnName(ColumnNumber: Word; pColumnName: PChar):
SQLResult;stdcall; function getColumnType(ColumnNumber: Word; var
puType: Word; var puSubType: Word): SQLResult; stdcall; function
getColumnLength(ColumnNumber: Word; var pLength:
LongWord):SQLResult; stdcall; function
getColumnPrecision(ColumnNumber: Word; var piPrecision: SmallInt):
SQLResult; stdcall; function getColumnScale(ColumnNumber: Word; var
piScale: SmallInt): SQLResult;stdcall; function
isNullable(ColumnNumber: Word; var Nullable: LongBool): SQLResult;
stdcall; function isAutoIncrement(ColumnNumber: Word; var AutoIncr:
LongBool): SQLResult; stdcall; function isReadOnly(ColumnNumber:
Word; var ReadOnly: LongBool): SQLResult; stdcall; function
isSearchable(ColumnNumber: Word; var Searchable: LongBool):
-
SQLResult; stdcall; function isBlobSizeExact(ColumnNumber: Word;
var IsExact: LongBool): SQLResult; stdcall; function next:
SQLResult; stdcall; function getString(ColumnNumber: Word; Value:
Pointer; var IsBlank: LongBool): SQLResult; stdcall; function
getShort(ColumnNumber: Word; Value: Pointer; var IsBlank:
LongBool): SQLResult; stdcall; function getLong(ColumnNumber: Word;
Value: Pointer; var IsBlank: LongBool): SQLResult; stdcall;
function getDouble(ColumnNumber: Word; Value: Pointer; var IsBlank:
LongBool): SQLResult; stdcall; function getBcd(ColumnNumber: Word;
Value: Pointer; var IsBlank: LongBool): SQLResult; stdcall;
function getTimeStamp(ColumnNumber: Word; Value: Pointer; var
IsBlank: LongBool): SQLResult; stdcall; function
getTime(ColumnNumber: Word; Value: Pointer; var IsBlank: LongBool):
SQLResult; stdcall; function getDate(ColumnNumber: Word; Value:
Pointer; var IsBlank: LongBool): SQLResult; stdcall; function
getBytes(ColumnNumber: Word; Value: Pointer; var IsBlank:
LongBool): SQLResult; stdcall; function getBlobSize(ColumnNumber:
Word; var Length: LongWord; var IsBlank: LongBool): SQLResult;
stdcall; function getBlob(ColumnNumber: Word; Value: Pointer; var
IsBlank: LongBool; Length: LongWord): SQLResult; stdcall;end; Essa
interface, juntamente com ISQLCommand, ISQLConnection e ISQLDriver
compem a arquitetura aberta do dbExpress. Se um desenvolvedor
quiser criar um driver dbExpress para acessar um banco de dados no
suportado nativamente, deve implementar essas interfaces.
TCustomSQLDataSet
A seguir, veremos as principais propriedades de
TCustomSQLDataSet, que a classe base para todos os DataSets
Unidirecionais do dbExpress. No listarei os membros herdados de
classes mais altas, como TDataSet, focando nos membros introduzidos
pela classe TCustomSQLDataSet: BlobBuffer Reserva buffer de memria
para armazenar campos BLOB.
CommandText Especifica o comando que o DataSet ir executar.
CommandType Indica o tipo de comando passado no CommandText, que
pode ser texto, o nome de uma tabela ou nome de uma stored
procedure.
CurrentBlobSize Tamanho do ultimo campo BLOB lido.
DataLink Identifica o Datalink que gerencia a comunicao entre o
DataSet e o DataSetMaster.
DataSource Faz um link ente o DataSet e outro DataSet
Master.
DesignerData Armazena dados customizados.
GetMetadata Especifica se o DataSet obtm metadatas do BD.
IndexDefs Contm definies de todos os ndices definidos para o
DataSet
InternalConnection Indica o componente que conecta o DataSet ao
BD.
LastError Indica o ultimo erro SQL retornado pelo dbExpress.
-
MaxBlobSize Indica o nmero mximo de bytes retornados por campos
BLOB.
NativeCommand Representa o comando SQL que foi enviado ao
servidor SQL.
NumericMapping Configurao de mapeamento entre campos BCD.
ParamCheck Especifica se a lista de parmetros para o Dataset
reconfigurada quando a consulta muda.
ParamCount Indica o nmero de parmetros do DataSet.
Params Parmetros da consulta SQL.
Prepared Indica se o comando est preparado para execuo.
ProcParams Descrio dos parmetros de Stored Procedures.
RecordCount Indica o nmero de registros obtidos pela consulta do
DataSet.
RowsAffected Indica o nmero de registros afetados pela execuo do
ltimo comando no DataSet.
SchemaInfo MetaDados do DataSet.
SortFieldNames Ordem dos dados da consulta quando o CommandType
for ctTable.
SQLConnection Componente de conexo.
TransactionLevel Nvel de isolamento de transao.
Veja a seguir os principais mtodos do componente:
CreateBlobStream Cria uma Stream para campos BLOB.
GetBlobFieldData Recupera o valor corrente do campo BLOB no
buffer.
GetDetailLinkFields Lista os campos da relao Master /
Detail.
GetFieldData Recupera o valor corrente de um campo no
buffer.
GetKeyFieldNames Preenche uma lista com os nomes de todos os
ndices do DataSet.
GetQuoteChar Retorna o character usado em comandos SQL para
manipular abertura e fechamento de strings.
IsSequenced Indica se o DataSet pode usar nmeros de registros
para indicar sua ordem.
Locate Para busca de registros no DataSet, com limitaes imposta
pelos cursores unidirecionais.
Lookup Para busca de campos Lookup no DataSet, com limitaes
imposta pelos cursores unidirecionais.
ParamByName Captura um parmetro pelo nome.
PrepareStatement Prepara a execuo do comando SQL.
SetSchemaInfo Indica se o Dataset representa metadados do
servidor e de que tipo.
Veja a seguir os principais eventos do componente:ParseDeleteSql
Ocorre quando a aplicao prepara para processar um comando DELETE
armazenado na propriedade
CommandText.ParseInsertSql Ocorre quando a aplicao prepara para
processar um comando INSERT armazenado na propriedade
CommandText.ParseSelectSql Ocorre quando a aplicao prepara para
processar um comando SELECT armazenado na propriedade
CommandText.ParseUpdateSql Ocorre quando a aplicao prepara para
processar um comando UPDATE armazenado na propriedade
CommandText.
Parte VI Recuperando MetaDados
-
Nesta parte do curso, veremos como utilizar o dbExpress para
recuperar informaes sobre objetos do banco de dados, como nome de
tabelas, campos, ndices (o que chamamos de metadados).
dbExpress e MetaDados
No dbExpress, a interface responsvel pela obteno de metadados a
ISQLMetaData, declarada na unit DBXpress.pas da seguinte forma:
ISQLMetaData = interface function SetOption(eDOption:
TSQLMetaDataOption; PropValue: LongInt): SQLResult; stdcall;
function GetOption(eDOption: TSQLMetaDataOption; PropValue:
Pointer; MaxLength: SmallInt; out Length: SmallInt): SQLResult;
stdcall; function getObjectList(eObjType: TSQLObjectType; out
Cursor: ISQLCursor): SQLResult; stdcall; function
getTables(TableName: PChar; TableType: LongWord; out Cursor:
ISQLCursor): SQLResult; stdcall; function
getProcedures(ProcedureName: PChar; ProcType: LongWord; out Cursor:
ISQLCursor): SQLResult; stdcall; function getColumns(TableName:
PChar; ColumnName: PChar; ColType: LongWord; Out Cursor:
ISQLCursor): SQLResult; stdcall; function
getProcedureParams(ProcName: PChar; ParamName: PChar; out Cursor:
ISQLCursor): SQLResult; stdcall; function getIndices(TableName:
PChar; IndexType: LongWord; out Cursor: ISQLCursor): SQLResult;
stdcall; function getErrorMessage(Error: PChar): SQLResult;
overload; stdcall; function getErrorMessageLen(out ErrorLen:
SmallInt): SQLResult; stdcall;end; Como voc pode notar pelos
mtodos, podemos recuperar praticamente qualquer informao do catlogo
do BD, como nomes de tabelas, tipos e nomes de campos, informaes
sobre Stored Procedures e mais. Para usar essa interface, devemos
usar o mtodo SetSchemaInfo de um DataSet do dbExpress.
Aplicao usando MetaDados
Inicie uma nova aplicao VCL no Delphi.
-
Figura 1.Coloque SQLConnection no e configure uma conexo para o
banco Employee do Interbase ou do Firebird (j discutimos conexes
anteriormente, de forma que no vou entrar em detalhes aqui).
Figura 2.Coloque mais alguns componentes dbExpress e da paleta
Data Access, conforme mostrado a seguir:
-
Figura 3.Configure o relacionamento entre os componentes da
seguinte forma: object DBGrid1: TDBGrid DataSource =
DataSource1endobject DBGrid2: TDBGrid DataSource =
DataSource2endobject SQLQuery1: TSQLQuery SQLConnection =
SQLConnection1endobject DataSetProvider1: TDataSetProvider DataSet
= SQLQuery1endobject ClientDataSet1: TClientDataSet ProviderName =
'DataSetProvider1'endobject DataSource1: TDataSource DataSet =
ClientDataSet1endobject SQLQuery2: TSQLQuery SQLConnection =
SQLConnection1endobject DataSetProvider2: TDataSetProvider
-
DataSet = SQLQuery2endobject ClientDataSet2: TClientDataSet
ProviderName = 'DataSetProvider2'endobject DataSource2: TDataSource
DataSet = ClientDataSet2endNo evento OnCreate do formulrio digite o
seguinte: procedure TForm1.FormCreate(Sender: TObject);begin
SQLQuery1.SetSchemaInfo(stTables,'',''); ClientDataSet1.Open;end; E
no evento OnDateChange do DataSource1 digite o seguinte: procedure
TForm1.DataSource1DataChange(Sender: TObject; Field: TField);var
tb: string;begin ClientDataSet2.Close; tb :=
ClientDataSet1.FieldByName('TABLE_NAME').AsString;
SQLQuery2.SetSchemaInfo(stColumns,tb,''); ClientDataSet2.Open;end;
Com isso, exibimos no primeiro DBGrid as tabelas do banco de dados.
Quando uma tabela for selecionada, exibimos informaes sobre suas
colunas no segundo DBGrid. Execute a aplicao:
Figura 4.
-
Parte VII ndices em memria A partir desta parte do curso, vamos
conhecer algumas tcnicas avanadas de desenvolvimento com dbExpress
e DataSnap, principalmente atravs do componente ClientDataSet.O
exemplo apresentado neste artigo mostra como definir ndices em
memria para o ClientDataSet. Para isso, basta setar a propriedade
IndexFieldNames (para criar ndices mais personalizados use a
propriedade IndexDefs e IndexName). No necessrio criar arquivos de
ndices como no Paradox ou refazer a consulta SQL no banco.Inicie
uma nova aplicao Delphi VCL e coloque no formulrio principal
coloque um ComboBox, Label, ClientDataSet, DataSource e DBGrid,
fazendo as ligaes como mostrado abaixo: object ClientDataSet1:
TClientDataSetend object DataSource1: TDataSource DataSet =
ClientDataSet1end object DBGrid1: TDBGrid DataSource =
DataSource1end D um clique de direita no ClientDataSet, escolha
Load from MyBase Table e abra o arquivo Customer.XML, localizado
nos demos do Delphi, por padro no diretrio: C:\Arquivos de
programas\Arquivos comuns\Borland Shared\Data Seu formulrio deve
estar semelhante ao mostrado a seguir:
Figura 1.No evento OnCreate do formulrio digite: procedure
TFrmMain.FormCreate(Sender: TObject);begin
ClientDataSet1.GetFieldNames(ComboBox1.Items);end; Isso preenche o
Combo com a lista de campos disponveis no ClientDataSet.
-
Figura 2.No evento OnChange do Combo, ordenamos o ClientDataSet
atravs do campo que o usurio selecionou: procedure
TFrmMain.ComboBox1Change(Sender: TObject);begin
ClientDataSet1.IndexFieldNames := ComboBox1.Text;end; Fazemos isso
tambm no OnTileClick do DBGrid, para ordenar o DataSet conforme a
coluna clicada no grid: procedure
TFrmMain.DBGrid1TitleClick(Column: TColumn);begin if
Assigned(OldColumn) then OldColumn.Title.Color :=
DBGrid1.FixedColor; ClientDataSet1.IndexFieldNames :=
Column.FieldName; Column.Title.Color := $00408080; OldColumn :=
Column; end; A figura a seguir mostra a aplicao em execuo, observe
o efeito de cor que colocamos na coluna:
Figura 3.
Parte VIII DataSetFields Este exemplo mostra como utilizar
DataSetFields em ClientDataSets. DataSetField um campo TField
especial que pode representar o contedo de outro DataSet.Configure
uma conexo dbExpress para o banco EMPLOYEE do Interbase. Coloque um
SQLConnection apontando para essa conexo. Coloque tambm duas
SQLQuery, um DataSetProvider, um ClientDataSet, dois DataSources,
um DBGrid, um Button e um
-
DBNavigator.Relacione os componentes conforme mostrado a seguir:
object SQLConnection1: TSQLConnectionend object SQLQuery1:
TSQLQuery SQLConnection = SQLConnection1End object SQLQuery2:
TSQLQuery SQLConnection = SQLConnection1End object DataSource1:
TDataSource DataSet = SQLQuery1End object DataSetProvider1:
TDataSetProvider DataSet = SQLQuery1End object DBGrid1: TDBGrid
DataSource = DataSource2End object DBNavigator1: TDBNavigator
DataSource = DataSource2End object ClientDataSet1: TClientDataSet
ProviderName = 'DataSetProvider1'End object DataSource2:
TDataSource DataSet = ClientDataSet1end Seu formulrio deve estar
semelhante ao mostrado a seguir:
-
Figura 1.Configura a instruo SQL da primeira Query para: select
* from CUSTOMER E da segunda para: select * from SALESwhere
CUST_NO=:CUST_NO Configure o parmetro (propriedade Params) da
segunda Query como mostrado a seguir:
Figura 2.Adicione todos os TField no ClientDataSet, e observe
que teremos um DataSetField:
-
Figura 3.Esse campo representa o DataSet detalhe. Com isso temos
no mesmo DataSet acesso a tabela principal (master) e tabela
detalhe (detail).No evento OnCreate do form digite: procedure
TFrmMain.FormCreate(Sender: TObject);begin ClientDataSet1.Open;end;
No evento OnClick do boto digite: procedure
TFrmMain.BitBtn1Click(Sender: TObject);begin
ClientDataSet1.ApplyUpdates(0);end; A figura a seguir mostra a
aplicao em execuo.
Figura 4.Navegue at a ltima coluna do DBGrid e observe que ela
lista o DataSetField. Clique em [...] e note que uma outra DBGrid
ser aberta automaticamente, mostrando dados da tabela detalhe,
relacionados com o registro selecionado no DBGrid principal.
-
Figura 5.Note que usamos para isso somente um nico
ClientDataSet. Alm disso, alteraes no DataSetField sero refletidos
no DataSet principal (por isso s precisamos de um ApplyUpdates, que
aplicar alteraes em ambas as tabelas no banco).
Parte IX InternalCalc Este exemplo mostra como utilizar o
recurso de InternalCalc em ClientDataSets, utilizado para otimizar
campos calculados em memria. Faremos um exemplo que vai compara
campos calculados tradicionais com InternalCalcs.O evento
OnCalcFields de um DataSet uma armadilha. Ele , obviamente,
utilizado para implementar campos calculados. O que poucos sabem
que esse evento chamado a todo o momento, por exemplo, quando um
valor de um campo muda, mesmo que esse campo no afete o valor do
clculo. Se voc escrever um cdigo mais complexo nesse evento, como
uma consulta ao banco de dados para obter um valor a ser usado no
clculo (o que considero um suicdio), ver que a performance da sua
aplicao cair consideravelmente. A soluo criar o campo como
InternalCalc ao invs de Calculated. A seguir, no evento
OnCalcFields, testamos se o estado (State) do DataSet
dsInternalCalc antes de fazermos o processamento, como no exemplo:
if ClientDataSet1.State = dsInternalCalc then
ClientDataSet1CAMPO.Value := ... Com isso, o cdigo ser executado
uma nica vez para cada registro, por exemplo quando h uma navegao
ou quando um Post chamado.Para ver um exemplo prtico, coloque um
ClientDataSet no formulrio e um DataSource. D um duplo clique no
componente e clique de direita no editor, escolhendo New Field
(vamos criar um DataSet de memria neste exemplo).Adicione o campo
NUM1 como mostrado a seguir:
-
Figura 1.Adicione o campo NUM2 como mostrado a seguir:
Figura 2.Adicione o campo DUMMY como mostrado a seguir:
-
Figura 3.Adicione o campo CALCULATED como mostrado a seguir
(observe que ele ser um campo calculado):
Figura 4.Adicione o campo INTERNALCALC como mostrado a seguir
(observe que ele ser um campo InternalCalc):
Figura 5.Arraste os campos criados para o formulrio, para que
sejam criados os controles Data-Aware. Coloque tambm um DBNavigator
apontando para o DataSource.Seu formulrio deve estar semelhante ao
mostrado a seguir:
-
Figura 6.Neste exemplo, vamos processar o clculo de NUM1 e NUM2
e atribuir a ambos os campos calculados, sem cada um de um tipo (um
internal e outro no). Para isso, no evento OnCalcFields do
ClientDataSet, digitamos: procedure
TFrmMain.ClientDataSet1CalcFields(DataSet: TDataSet);begin
inherited; { calculado normal} ClientDataSet1CALCULATED.AsInteger
:= ClientDataSet1NUM1.AsInteger + ClientDataSet1NUM2.AsInteger;
{internal calc} if ClientDataSet1.State = dsInternalCalc then
ClientDataSet1INTERNALCALC.AsInteger :=
ClientDataSet1NUM1.AsInteger + ClientDataSet1NUM2.AsInteger;end;
Execute a aplicao e digite dois valores, para NUM1 e NUM2:
Figura 7.Para ver o campo em ao, coloque dois Brekpoints no
cdigo conforme mostrado a seguir:
-
Figura 8.Agora digite um valor qualquer no campo DUMMY, observe
que ele no afeta o clculo, pois no serve para nada (no participa da
soma). Mesmo assim, note que o cdigo do campo calculado ser
executado, mesmo que a soma no seja afetada por DUMMY. O processo
no o mesmo para o campo InternalCalc (no segundo breakpoint), ele
ser executado somente uma vez para fazer a soma, quando voc der o
Post no DataSet (mais otimizado).
Figura 9.
Parte X UpdateStatus Este exemplo mostra como utilizar as
propriedades UpdateStatus e UpdateFilter do ClientDataSet. Enquanto
o State indica o estado de um DataSet inteiro, UpdateStatus
representa o estado atual de um registro. StatusFilter permite
exibir filtrar os registros de acordo com seu estado.Inicie uma
nova aplicao Delphi VCL e coloque no formulrio principal um
ComboBox e um ClientDataSet. D um clique de direita no
ClientDataSet, escolha Load from MyBase Table e abra o arquivo
Customer.XML, localizado nos demos do Delphi, por padro no
diretrio:
-
C:\Arquivos de programas\Arquivos comuns\Borland Shared\Data
Adicione os TFields no DataSet e adicione um campo calculado,
chamado STATUS, conforme mostrado a seguir:
Figura 1.Arraste os campos para o formulrio para criar os
controles Data-Aware. Coloque tambm um DBNavigator apontando para o
DataSource.Seu formulrio deve estar semelhante ao mostrado a
seguir:
Figura 2.No OnCreate do form, digite: uses TypInfo;...procedure
TFrmMain.FormCreate(Sender: TObject);var i : integer;begin for i :=
0 to 3 do
ComboBox1.Items.Add(GetEnumName(TypeInfo(TUpdateStatus),I));
ComboBox1.Items.Add('Todos');end; Isso preenche o Combo com os
possveis valores para a enumerao TUpdateStatus: TUpdateStatus =
(usUnmodified, usModified, usInserted, usDeleted); TUpdateStatusSet
= set of TUpdateStatus;
-
Figura 3.No evento OnCalcFields digite: procedure
TFrmMain.ClientDataSet1CalcFields(DataSet: TDataSet);begin
ClientDataSet1STATUS.AsString :=
GetEnumName(TypeInfo(TUpdateStatus),Integer(ClientDataSet1.UpdateStatus));end;
Isso faz com que o campo STATUS indique o status atual do
registro.Quando o usurio selecione um Status na combo, vamos exibir
somente os registros que estejam naquele estado (excludo, inserido,
modificado etc.). Isso feito no evento OnChange do Combo: procedure
TFrmMain.ComboBox1Change(Sender: TObject);begin if
ComboBox1.ItemIndex = 4 then ClientDataSet1.StatusFilter := [] else
ClientDataSet1.StatusFilter :=
[TUpdateStatus(ComboBox1.ItemIndex)];end; Execute a aplicao,
insira, modifique e exclua alguns registros, e a seguir escolha o
filtro.
Figura 4.
Parte XI Data vs. Delta Este exemplo apresenta o uso das
propriedades Data e Delta do ClientDataSet. Data um OLEVariant que
armazena a cache de dados, Delta um OLEVariant que armazena as
ALTERAES feitas em um ClientDataSet.Inicie uma nova aplicao Delphi
VCL e coloque no formulrio principal coloque um Button, dois
ClientDataSets, dois DataSource2, dois DBGridse um DBNavigator,
fazendo as ligaes como mostrado abaixo: object ClientDataSet1:
TClientDataSetend
-
object DataSource1: TDataSource DataSet = ClientDataSet1end
object DBGrid1: TDBGrid DataSource = DataSource1end object
ClientDataSet2: TClientDataSetend object DataSource2: TDataSource
DataSet = ClientDataSet2end object DBGrid2: TDBGrid DataSource =
DataSource2end object DBNavigator1: TDBNavigator DataSource =
DataSource1end D um clique de direita no ClientDataSet, escolha
Load from MyBase Table e abra o arquivo Customer.XML, localizado
nos demos do Delphi, por padro no diretrio: C:\Arquivos de
programas\Arquivos comuns\Borland Shared\Data Seu formulrio deve
estar semelhante ao mostrado a seguir:
Figura 1.No evento OnClick do boto capturamos o Delta do
primeiro CDS e jogamos como Data do segundo CDS:
-
procedure TFrmMain.BitBtn1Click(Sender: TObject);begin if
ClientDataSet1.ChangeCount > 0 then ClientDataSet2.Data :=
ClientDataSet1.Delta;end; Execute a aplicao. Faa algumas modificaes
no primeiro DBGrid e observe que, ao clicar no boto, essas alteraes
so registradas no segundo DBGrid:
Figura 2.No exemplo anterior, modifiquei o campo Company de um
registro. O CDS mantm os valores originais em Delta para serem
utilizados no processo de Resolving do DataSetProvider, quando os
campos precisam ser processados em instrues SQL de atualizao.
Parte XII ClientDataSet e XML Neste artigo veremos alguns
interessantes recursos do ClientDataSet para suporte a XML. Atravs
de uma aplicao dbExpress tpica, vamos salvar os dados obtidos em
XML para o disco, ler, examinar a estrutura do DataPacket e o XML
em memria.O suporte a XML no se restringe simplesmente ao
salvamento dos dados para o disco em um formato estruturado. A
Borland inclui essa funcionalidade no componente para que possamos
facilmente trafegar DataSets pela Web em formato XML, usando o SOAP
(Simple Object Access Protocol).Configure uma conexo dbExpress para
o banco EMPLOYEE do Interbase. Coloque um SQLConnection apontando
para essa conexo. Coloque tambm duas SQLQuery, um DataSetProvider,
um ClientDataSet, dois DataSources, um DBGrid, quatro Buttons, um
DBNavigator, um Memo e um TWebBrowser (paleta Internet).Relacione
os componentes conforme mostrado a seguir: object SQLConnection1:
TSQLConnectionend object SQLQuery1: TSQLQuery SQLConnection =
SQLConnection1
-
End object SQLQuery1: TSQLQuery SQLConnection =
SQLConnection1End object DataSetProvider1: TDataSetProvider DataSet
= SQLQuery1End object DBGrid1: TDBGrid DataSource = DataSource2End
object DBNavigator1: TDBNavigator DataSource = DataSource2End
object ClientDataSet1: TClientDataSet ProviderName =
'DataSetProvider1'End A instruo SQL da SQLQuery1 mostrada a seguir:
select * from EMPLOYEE Seu formulrio deve estar semelhante ao
mostrado a seguir:
-
Figura 1. O cdigo do boto Open mostrado a seguir: procedure
TForm1.btnOpenClick(Sender: TObject);begin ClientDataSet1.Open;end;
O cdigo do boto Save XML mostrado a seguir: procedure
TForm1.Button2Click(Sender: TObject);begin
ClientDataSet1.SaveToFile('c:\Employee.xml');
WebBrowser1.Navigate('file:///c:\Employee.xml');end; O cdigo do
boto Load XML mostrado a seguir: procedure
TForm1.Button3Click(Sender: TObject);begin
ClientDataSet1.LoadFromFile('c:\Employee.xml');end; O cdigo do boto
XML Data mostrado a seguir:
-
procedure TForm1.Button4Click(Sender: TObject);begin
Memo1.Lines.Text := ClientDataSet1.XMLData;end; Execute a aplicao e
clique nos botes Open, Save XML e XML Data. Observe o resultado na
figura a seguir:
Figura 2.A propriedade XMLData, somente-leitura, permite o
acesso as dados em XML do ClientDataSet sem a necessidade de
salvamento no disco, ideal para por exemplo trafegarmos informaes
via Web Services / SOAP.O mtodo SaveToFile salva o DataPacket para
o disco. Usamos um WebBrowser para exibir o XML dentro do form,
atravs de um plugin do Internet Explorer usado pelo componente.
Observe que voc pode expandir e reduzir nodes. Tambm possvel fazer
isso no browser, claro:
-
Figura 3.Inclua, exclua, altere alguns registros no DBGrid e
salve novamente o XML. Abra o arquivo para examinarmos seu formato.
xml version="1.0" standalone="yes" ?> - - -
-
FIELDS> METADATA>- ... demais registros omitidos
ROWDATA> DATAPACKET> Um DataPacket contm duas sees
principais: MetaData e RowData. Na seo MetaData temos informaes que
definem os campos do DataSet (Fields) e tambm os parmetros extras
que inclumos no DataPacket (Params). Em RowData temos informaes
sobre os registros, inclusive que foram modificados. Finalmente,
execute a aplicao e carregue o DataSet sem efetuar a consulta ao
BD, clicando em Load XML:
Figura 4.Lembre-se que muitas operaes podem ser realizadas em
tempo de design, clicando-se de direita sobre o ClientDataSet:
Figura 5.Uma ltima dica que voc pode roubar dados de qualquer
tipo de DataSet em designtime e jogar na memria do ClientDataSet.
Por exemplo, coloque uma Table do BDE apontando para
-
DBDemos>Employee.DB. Coloque um ClientDataSet, d um clique de
direita sobre ele escolha a opo Assign Local Data:
Figura 6.
Figura 7.Observe que os dados foram colocados no Data do CDS
(veja o arquivo DFM abaixo):
-
Figura 8.
Agora voc pode acessar os menus de contexto e salvar para XML
fcil e rapidamente. Ideal para extrair dados de qualquer banco e
salvar rapidamente em XML.
Parte XIII Aplicaes desconectadas
Neste exemplo, veremos como usar o dbExpress e ClientDataSet
para obter dados do banco de dados, sem no entanto manter uma
conexo sempre ativa com o mesmo. Isso far com que suas aplicaes se
tornem mais escalveis e apresentem uma melhor performance quando o
nmero de clientes aumentar. Para isso, vamos fazer o seguinte:
abriremos a conexo e o cursor de dados somente o tempo mnimo
necessrio para executar o select. A partir da, o DataSetProvider ir
empacotar os dados em um DataPacket que ser armazenado no Data do
ClientDataSet. A seguir, fechamos a consulta e a conexo, e o usurio
poder continuar trabalhando normalmente (com dados em cache).
Quando for necessrio aplicar os dados no servidor, abrimos
novamente a conexo e enviamos o Delta.O mais interessante de tudo
que o prprio DataSetProvider j realiza a maioria desse trabalho
para ns: ele sabe exatamente o momento que se faz necessria uma
conexo, se encarrega de abri-la, executar a query (que tambm aberta
por ele) e assim por diante. Precisamos apenar configurar algumas
propriedades. Vamos a prtica.Configure uma conexo dbExpress para o
banco EMPLOYEE do Interbase. Coloque um SQLConnection apontando
para essa conexo. Coloque tambm um SQLQuery, um DataSetProvider, um
ClientDataSet, um DataSource, um DBGrid, dois Buttons e um Memo.
Configure os componentes conforme o cdigo a seguir: object DBGrid1:
TDBGrid
-
DataSource = DataSource1 end object DataSource1: TDataSource
DataSet = ClientDataSet1 end object ClientDataSet1: TClientDataSet
ProviderName = 'DataSetProvider1' end object DataSetProvider1:
TDataSetProvider DataSet = SQLQuery1 end object SQLQuery1:
TSQLQuery SQLConnection = SQLConnection1 end object SQLConnection1:
TSQLConnection ConnectionName = 'EMPLOYEE' LoginPrompt = False
Connected = False KeepConnection = False Params.Strings = (
'DriverName=Interbase'
'Database=C:\Borland\InterBase\examples\database\employee.gdb'
'User_Name=sysdba' 'Password=masterkey' 'ServerCharSet=WIN1252'
'SQLDialect=3') end Seu formulrio deve estar semelhante ao mostrado
a seguir:
-
Figura 1.
-
Figura 2.Observe como configurei as propriedades Connected e
KeepConnection do SQLConnection, ambas para False. A SQLQuery tambm
tem o Active configurado para False.O cdigo do boto Executar
repassa para a SQLQuery a instruo SQL (Select) digitada no Memo:
procedure TFrmMain.BitBtn1Click(Sender: TObject);begin
SQLQuery1.SQL.Assign(Memo1.Lines); ClientDataSet1.Close;
ClientDataSet1.Open;end; Execute a aplicao, digite uma instruo SQL
e clique no boto Executar (veja a figura a seguir).
-
Figura 3.Quando fazemos a consulta, os seguintes procedimentos
foram realizados pelo dbExpress:
ClientDataSet (CDS) recebe a chamada ao Open; CDS chama o mtodo
de interface As_GetRecords; DataSetProvider (DSP) recebe a chamada
ao GetRecords; DSP abre a conexo (SQLConnection); DSP abre o cursor
da SQLQuery; DSP varre os dados da consulta; DSP empacota os dados;
DSP fecha o cursor (SQLQuery); DSP verifica a propriedade
KeepConnection, como est False, fecha a conexo com
o BD; DSP envia o DATA ao CDS; CDS fica com dados em memria.
Mesmo com a aplicao em execuo, podemos comprovar que no h
nenhuma conexo ativa com o servidor.
-
Figura 4.O boto Apply simplesmente chama o ApplyUpdates para
atualizar o BD: o DSP se encarregar de abrir todas conexes
necessrias e novamente, fechar aps a operao. Use o KeepConnection
com cuidado: em um cenrio onde o cliente ir frequentemente enviar
solicitaes (selects, updates etc.) ao BD, no uma boa soluo ter essa
propriedade configurada. Ela ideal para quando o usurio ir
trabalhar de forma desconectada, em um conjunto de dados maior, por
um longo perodo de tempo.Outro lembrete: no use o Packet Records
com essa abordagem, pois exige intensa comunicao com o servidor
SQL. Pelo motivo anterior, essa combinao no faria sentido.
Parte XIV Cache BDE x Cach ClientDataSet
Neste exemplo vamos estudar um pouco sobre o mecanismo de cache
do ClientDataSet. Aproveitarei para fazer alguns comparativos com o
mecanismo de cache do BDE, para aqueles que esto migrando de soluo.
Ou seja, a aplicao que preparei um comparativo entre o mecanismo de
cache do BDE (Cache Updates) e a cache (Data) do ClientDataSet.
Configure uma conexo dbExpress para o banco EMPLOYEE do Interbase.
Coloque um SQLConnection apontando para essa conexo. Coloque tambm
um SQLQuery, um DataSetProvider, um ClientDataSet, um DataSource,
dois DBGrids e dois Buttons. Da mesma forma, configure um acesso
BDE para o mesmo banco de dados. Os componentes so: DataBase,
Query, UpdateSQL e DataSource. Configure-os como mostrado na
listagem a seguir: object DBGrid1: TDBGrid DataSource = DataSource1
end object DBGrid2: TDBGrid DataSource = DataSource2 end object
SQLConnection1: TSQLConnection
-
ConnectionName = 'EMPLOYEE' DriverName = 'Interbase'
GetDriverFunc = 'getSQLDriverINTERBASE' LibraryName =
'dbexpint.dll' LoginPrompt = False Params.Strings = (
'DriverName=Interbase'
'Database=C:\Borland\InterBase\examples\database\employee.gdb'
'RoleName=RoleName' 'User_Name=sysdba' 'Password=masterkey'
'ServerCharSet=' 'SQLDialect=3' 'BlobSize=-1' 'CommitRetain=False'
'WaitOnLocks=True' 'ErrorResourceFile=' 'LocaleCode=0000'
'Interbase TransIsolation=ReadCommited' 'Trim Char=False')
VendorLib = 'gds32.dll' end object SQLQuery1: TSQLQuery SQL.Strings
= ( 'select * from CUSTOMER') SQLConnection = SQLConnection1 end
object DataSource1: TDataSource DataSet = ClientDataSet1 end object
Database1: TDatabase DatabaseName = 'LOCAL' DriverName = 'INTRBASE'
LoginPrompt = False Params.Strings = ( 'SERVER
NAME=IB_SERVER:/PATH/DATABASE.GDB' 'USER NAME=SYSDBA' 'OPEN
MODE=READ/WRITE' 'SCHEMA CACHE SIZE=8' 'LANGDRIVER=' 'SQLQRYMODE='
'SQLPASSTHRU MODE=SHARED AUTOCOMMIT' 'SCHEMA CACHE TIME=-1' 'MAX
ROWS=-1' 'BATCH COUNT=200' 'ENABLE SCHEMA CACHE=FALSE' 'SCHEMA
CACHE DIR=' 'ENABLE BCD=FALSE' 'BLOBS TO CACHE=64' 'BLOB SIZE=32'
'WAIT ON LOCKS=FALSE'
-
'COMMIT RETAIN=FALSE' 'ROLE NAME=' 'PASSWORD=masterkey')
SessionName = 'Default' end object Query1: TQuery CachedUpdates =
True DatabaseName = 'LOCAL' SQL.Strings = ( 'select * from
CUSTOMER') UpdateObject = UpdateSQL1 end object UpdateSQL1:
TUpdateSQL end object DataSetProvider1: TDataSetProvider DataSet =
SQLQuery1 end object ClientDataSet1: TClientDataSet ProviderName =
'DataSetProvider1' end object DataSource2: TDataSource DataSet =
Query1 end Seu formulrio deve estar semelhante ao mostrado a
seguir:
-
Figura 1.Para ver como a cache funciona em ambos os engines,
criei um mtodo que adiciona 5 mil registros em um DataSet passado
como parmetro. Ele gera valores strings randomizados para cada
ccampo da tabela: procedure TFrmMain.AppendRandomRecords(DataSet:
TDataSet);var i : integer;begin DataSet.Open; for i := 1 to 5000 do
begin DataSet.Append; DataSet.FieldByName('CUST_NO').AsInteger :=
I; DataSet.FieldByName('CUSTOMER').AsString := RandomStr;
DataSet.FieldByName('CONTACT_FIRST').AsString := RandomStr;
DataSet.FieldByName('CONTACT_LAST').AsString := RandomStr;
DataSet.FieldByName('ADDRESS_LINE1').AsString := RandomStr;
DataSet.FieldByName('ADDRESS_LINE2').AsString := RandomStr;
DataSet.FieldByName('STATE_PROVINCE').AsString := RandomStr;
DataSet.POST; Application.ProcessMessages; end;end;
-
No boto Iniciar de cada engine, chamamos o mtodo passando o
respectivo DataSet (Query do BDE ou ClientDataSet do DataSnap):
procedure TFrmMain.BitBtn1Click(Sender: TObject);begin
AppendRandomRecords(ClientDataSet1);end; procedure
TFrmMain.BitBtn2Click(Sender: TObject);begin
AppendRandomRecords(Query1);end; Aps iniciar a insero de registros
aleatrios, observe que a aplicao consome mais memria (Task Manager)
quando se usa o CDS. Isso comprova que todas as alteraes e updates
ficam no processo da aplicao, em memria.
Figura 2.
-
Figura 3.No BDE, so criados arquivos no disco (veja dir. atual
da aplicao) para armazenar as atualizaes, como comprova a figura a
seguir.
-
Figura 4.
Figura 5.Com isso, vemos que o DataSnap usa um mecanismo de
cache muito mais inteligente e
-
efetivo. Aplicaes com DataSnap sero bem mais escalveis e rpidas.
O BDE no foi feito e no est preparado para a criao de solues desse
tipo, deficincia que foi suprida pelo dbExpress e
ClientDataSet.
Parte XV Packet Records
Neste exemplo veremos como usar o recurso de Packet Records do
ClientDataSet. Configurando essa propriedade, podemos instruir ao
servidor de aplicao que empacote e envie dados por demanda. Quando
temos um grande select no servidor, podemos instruir ao
DataSetProvider que v enviando essas informaes aos poucos, para
otimizar o trfego de dados. importante notar desde j, que essa
abordagem menos escalvel: necessrio manter o cursor e conexo ativos
durante todo o processo.Configure uma conexo dbExpress para o banco
EMPLOYEE do Interbase. Coloque um SQLConnection apontando para essa
conexo. Coloque tambm um SQLQuery, um DataSetProvider, um
ClientDataSet, um DataSource, um DBGrid, um DBNavigator, um Memo e
um SQLMonitor. Para configurar o recurso em tempo de execuo,
coloque tambm um RadioGroup, dois CheckBoxes, um SpinEdit. A Figura
a seguir mostra como ficou o form:
Figura 1.
A listagem a seguir mostra como cada componente foi configurado:
object DBGrid1: TDBGrid DataSource = DataSource1 end object
DBNavigator1: TDBNavigator DataSource = DataSource1 end object
SQLConnection1: TSQLConnection ConnectionName = 'EMPLOYEE'
DriverName = 'Interbase' GetDriverFunc = 'getSQLDriverINTERBASE'
LibraryName = 'dbexpint.dll' LoadParamsOnConnect = True LoginPrompt
= False Params.Strings = ( 'DriverName=Interbase'
'Database=C:\Borland\InterBase\examples\database\employee.gdb'
'RoleName=RoleName' 'User_Name=sysdba' 'Password=masterkey'
'ServerCharSet=' 'SQLDialect=3' 'BlobSize=-1' 'CommitRetain=False'
'WaitOnLocks=True' 'ErrorResourceFile=' 'LocaleCode=0000'
'Interbase TransIsolation=ReadCommited' 'Trim Char=False')
VendorLib = 'gds32.dll'
-
Left = 72 Top = 232 end object SQLQuery1: TSQLQuery SQL.Strings
= ( 'select * from CUSTOMER') SQLConnection = SQLConnection1 end
object DataSetProvider1: TDataSetProvider DataSet = SQLQuery1 end
object ClientDataSet1: TClientDataSet ProviderName =
'DataSetProvider1' end object DataSource1: TDataSource DataSet =
ClientDataSet1 end object SQLMonitor1: TSQLMonitor SQLConnection =
SQLConnection1 end No boto Refresh verificamos as configuraes de
tela e configuramos o Packet Records do ClientDataSet: procedure
TFrmMain.BitBtn2Click(Sender: TObject);begin Memo1.Lines.Clear; if
CheckBox1.Checked then ClientDataSet1.PacketRecords :=
SpinEdit1.Value else ClientDataSet1.PacketRecords := -1;
ClientDataSet1.Close; ClientDataSet1.Open;end; No OnChange do
SpinEdit tambm mudamos essa configurao: procedure
TFrmMain.SpinEdit1Change(Sender: TObject);begin
ClientDataSet1.PacketRecords := SpinEdit1.Value;end; No boto
GetNextPacket chamamos o mtodo de mesmo nome do ClienDataSet:
procedure TFrmMain.BitBtn1Click(Sender: TObject);begin
ClientDataSet1.GetNextPacket;end; No CheckBox com o nome de
FetchOnDemand configuramos a propriedade de mesmo do ClienDataSet:
procedure TFrmMain.CheckBox2Click(Sender: TObject);begin
-
BitBtn1.Enabled := not CheckBox2.Checked;
ClientDataSet1.FetchOnDemand := CheckBox2.Checked;end; No CheckBox
usar PacketRecords fazemos alguns ajustes visuais: procedure
TFrmMain.CheckBox1Click(Sender: TObject);var i : integer;begin
GroupBox1.Enabled := CheckBox1.Checked; Label1.Enabled :=
CheckBox1.Checked; SpinEdit1.Enabled := CheckBox1.Checked;
CheckBox2.Enabled := CheckBox1.Checked; BitBtn1.Enabled :=
(CheckBox1.Checked) and not (CheckBox2.Checked);end; No evento
OnLogTrace do SQLMonitor vamos monitorar a comunicao com o IB/FB
para verificar quando e quais comandos esto sendo executados,
jogando essas informaes para um Memo: procedure
TFrmMain.SQLMonitor1LogTrace(Sender: TObject; CBInfo:
pSQLTRACEDesc);begin Memo1.Lines.Add(String(CBInfo.pszTrace));end;
Vamos executar a aplicao e fazer alguns testes, usando diferentes
configuraes. Dessa forma, vamos entender exatamente como o
PacketRecords funciona. 1 Sem usar PacketRecords - clique no boto
Refresh e veja o comportamento na figura a seguir. Todos os dados
retornados pelo Select foram empacotados e jogados na memria (Data)
do ClienDataSet. Nenhum recurso do servidor, incluindo conexo e
cursor, fica preso. No entanto, o tempo necessrio e trfego de rede
usado para transferir o packet maior.
Figura 2.2 Usando PacketRecords com FetchOnDemand ativado -
clique no boto Refresh e veja o comportamento na figura a seguir.
Configuramos o packet size como 3, o que indica que os registros
sero empacotados de 3 em trs. O prprio ClientDataSet detecta quando
mais registros so necessrio e traz por demanda (da o nome
FetchOnDemand). Isso pode acontecer tanto por necessidade do usurio
(confirme isso navegando no DBGrid para baixo) ou programaticamente
(via chamadas subseqentes ao mtodo Next). Observe que cada Fetch
envolve, lgico, uma chamada ao servidor SQL. A soluo mais otimizada
para grandes resultsets, mais vai consumir mais recursos do BD,
pois o cursor e conexo ficam presos aguardando novas solicitaes de
packet.
Figura 3.3 Usando PacketRecords com FetchOnDemand desativado -
clique no boto Refresh e veja o comportamento na figura a seguir.
Configuramos o packet size como 3, o que indica que os registros
sero empacotados de 3 em trs. A diferena em no usar o FetchOnDemand
que voc deve solicitar novos pacotes de dados, atravs do mtodo
GetNextPacket do CDS (fizemos isso no boto). Faa um teste navegando
at o ltimo registro do DBGrid, observe que ele trava no terceiro.
Clicando no boto GetNextPacket, mais trs registros sero
-
trazidos. Cada Fetch envolve, lgico, uma chamada ao servidor
SQL. A soluo mais otimizada para grandes resultsets, mais vai
consumir mais recursos do BD, pois o cursor e conexo ficam presos
aguardando novas solicitaes de packet.
Figura 4.
Parte XVI Save Point
Neste artigo veremos como usar o interessante recurso de
SavePoint do ClientDataSet. Esse recursos permite que voc tire um
foto do atual status da memria do ClientDataSet, e recupere este
status a qualquer momento. Voc pode, por exemplo, salvar as atuais
alteraes do CDS em memria, fazer novas alteraes e a seguir
desfaze-las, voltando ao estado original previamente sinalizado.
Imagine isso como uma espcie de transaes em memria, com Rollback e
Commit. Para ver como isso funciona na prtica, preparei um exemplo
interessante.Coloque os componentes no formulrio conforme mostrado
na figura a seguir. Aqui colocamos um ClientDataSet, um DataSource
e trs Buttons. D um clique de direita no ClientDataSet, escolha
Load from MyBase Table e abra o arquivo Customer.XML, localizado
nos demos do Delphi, por padro no diretrio C:\Arquivos de
programas\Arquivos comuns\Borland Shared\Data. Arraste os TFields
para o form para criar os controles Data-Aware.
Figura 1.O cdigo DFM do form mostrado a seguir: object
ClientDataSet1: TClientDataSet Active = True object
ClientDataSet1CustNo: TFloatField FieldName = 'CustNo' end object
ClientDataSet1Company: TStringField FieldName = 'Company' Size = 30
end object ClientDataSet1Addr1: TStringField FieldName = 'Addr1'
Size = 30 end object ClientDataSet1Addr2: TStringField FieldName =
'Addr2' Size = 30 end object ClientDataSet1City: TStringField
FieldName = 'City' Size = 15 end object ClientDataSet1State:
TStringField FieldName = 'State' end object ClientDataSet1Zip:
TStringField FieldName = 'Zip'
-
Size = 10 end object ClientDataSet1Country: TStringField
FieldName = 'Country' end object ClientDataSet1Phone: TStringField
FieldName = 'Phone' Size = 15 end object ClientDataSet1FAX:
TStringField FieldName = 'FAX' Size = 15 end object
ClientDataSet1TaxRate: TFloatField FieldName = 'TaxRate' end object
ClientDataSet1Contact: TStringField FieldName = 'Contact' end
object ClientDataSet1LastInvoiceDate: TDateTimeField FieldName =
'LastInvoiceDate' endendobject DataSource1: TDataSource DataSet =
ClientDataSet1endobject DBGrid1: TDBGrid DataSource =
DataSource1endobject DBNavigator1: TDBNavigator DataSource =
DataSource1end O cdigo do boto SavePoint salve o estado atual do
ClientDataSet: procedure TFrmMain.BitBtn2Click(Sender:
TObject);begin MyPoint := ClientDataSet1.SavePoint;end; MyPoint uma
varivel declara no form, com o seguinte tipo: public MyPoint :
integer; Para recuperar o estado, basta atribuir novamente essa
propriedade ao SavePoint, observe (fizemos isso no outro boto):
procedure TFrmMain.BitBtn3Click(Sender: TObject);begin
ClientDataSet1.SavePoint := MyPoint;end; Para voltar ao ltimo
estado, chamamos o UndoLastChanges no boto de mesmo nome:
-
procedure TFrmMain.BitBtn1Click(Sender: TObject);begin
ClientDataSet1.UndoLastChange(true);end; Executando a aplicao,
vamos fazer alguns testes para ver como recurso funciona. Altere o
Company colocando TESTE1 ao final, clique em Post no DBNavigator e
a seguir no boto SavePoint. Seguindo os mesmos passos, altere para
TESTE2 e TESTE3, dando sempre um post e savepoint ao final.
Figura 2.Agora clique vrias vezes no boto UnLastChanges e
verifique que o estado atual de cada savepoint recuperado a cada
chamado a mtodo, como transaes de BD, porm, tudo em memria.
Parte XVII Record Count e RecNo
Neste artigo veremos como usar as propriedades RecordCount e
RecNo do ClienDataSet. Essas propriedades so exclusivas desse
componente, sendo que possuam capacidades limitadas quando se
trabalhava com BDE. RecordCount, por exemplo, sempre retornava -1
em Queries de consultas a banco de dados quando se usa o BDE. Essa
limitao no existe no ClientDataSet, pois o mesmo possui todas as
informaes em memria.
RecordCount retorne o nmero atual de registros na memria do
ClientDataSet, ou seja, o nmero de registros retornados pelo Select
associado (a menos que se use Packet Records).
RecNo a posio atual do cursor local de dados na memria do
ClientDataSet. Por exemplo, se esse valor for 5, estamos navegando
no quinto registro do ClientDataSet.
Vejamos como isso funciona na prtica. Coloque os componentes no
formulrio conforme mostrado na figura a seguir. Aqui colocamos um
ClientDataSet, um DataSource, um DBGrid, um Button e um Label. D um
clique de direita no ClientDataSet, escolha Load from MyBase Table
e abra o arquivo Customer.XML, localizado nos demos do Delphi, por
padro no diretrio C:\Arquivos de programas\Arquivos comuns\Borland
Shared\Data.
Figura 1.No boto simplesmente jogamos o valor de RecordCount no
Caption do Label. procedure TFrmMain.BitBtn1Click(Sender:
TObject);begin Label1.Caption :=
IntToStr(ClientDataSet1.RecordCount);end; Tambm criamos um campo
calculado no ClientDataSet, como mostrado a seguir. Ele do tipo
Integer e vai indicar a posio atual do registro no resulset, se
comportando como se fosse um campo normal do BD (como cdigo, ID
etc.)
Figura 2.
-
Figura 3.
No OnCalcFields atribumos o valor do campo com base no valor do
RecNo atual do ClientDataSet: procedure
TFrmMain.ClientDataSet1CalcFields(DataSet: TDataSet);begin
ClientDataSet1RECNO.AsInteger := ClientDataSet1.RecNo;end; Observe
ambos RecordCount e RecNo em ao na figura a seguir.
Figura 4.Voc tambm pode fazer um DBGrid zebrado usado o RecNo,
bastando verificar se o ndice do registro mpar o par. Isso no
possvel no BDE, pois sempre retorna -1. Isso pode ser feito com o
seguinte cdigo no evento OnDrawColumnCell do DBGrid: procedure
TFrmMain.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect;
DataCol: Integer; Column: TColumn; State: TGridDrawState);begin
(*DBGrid zebrado*) if not odd(ClientDataSet1.RecNo) then // se for
mpar if not (gdSelected in State) then // se a clula no est
selecionada begin DBGrid1.Canvas.Brush.Color:= clYellow; // define
uma cor de fundo DBGrid1.Canvas.FillRect(Rect); // pinta a clula
DBGrid1.DefaultDrawDataCell(rect,Column.Field,State); // pinta o
texto padro end;end; O resultado mostrado na figura a seguir:
Figura 5.
Parte XIX Usando interfaces
Em DataSnap e qualquer arquitetura de objetos distribudos,
faz-se uso intensivo de interfaces. importantssimo que voc saiba e
compreenda como feita a comunicao entre cliente e servidor de
aplicao, atravs do uso desse recurso. Interfaces so usadas
amplamente nos bastidores de um arquitetura DataSnap.Dessa forma,
decido incluir um captulo introdutrio neste curso, que mostra como
usar interfaces puras (no relacionadas ao DataSnap, mas a POO).
Entendendo o princpio bsico aqui proposto, ser base para
entendermos abordagens mais complexas quando estudarmos o COM, MTS,
COM+ e SOAP.Uma interface semelhante a uma classe que possua
somente mtodos abstratos, ou seja, sem implementao. Uma interface
apenas define mtodos que depois devem ser implementados por uma
classe. Dessa forma, um objeto pode se comunicar com o outro apenas
conhecendo a sua interface, que funciona como uma espcie de
contrato.
-
Figura 1.Uma interface como se fosse um controle remoto. Voc
consegue interagir com um objeto conhecendo o que ele oferece,
tendo a interface que descreve cada funo, porm, sem a mnima idia de
como ele implementa essa funcionalidade internamente.Assim como
TObject a classe base para todas as classes do Delphi, a interface
base para todas as interfaces IInterface. A interface base para
todos as interfaces COM IUnknown. IUnknown na verdade apenas um
alias para IInterface. Uma Interface no tem cdigo de implementao
associado. Ateno - O uso de Interfaces um recurso da linguagem
Delphi, de forma que voc poder utilizar interfaces mesmo que no
esteja programando objetos distribudos. Veja a seguir a declarao de
IInterface: IInterface = interface
['{00000000-0000-0000-C000-000000000046}'] function
QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall; function__Release: Integer;
stdcall;end; Os mtodos _AddRef e _Release definem o mecanismo de
contagem de referncia. Isso significa que voc no precisa liberar um
objeto que implementa IInterface. QueryInterface faz solicitaes
dinamicamente um objeto para obter uma referncia para as interfaces
que ele suporta. Para demonstrar o uso de interfaces vamos criar um
pequeno exemplo. Inicie uma nova aplicao no Delphi. Salve o
formulrio como uFrmMain.pas e o projeto Interfaces.dpr. D o nome de
FrmMain ao formulrio.Abra a unit do formulrio e declara a seguinte
interface na seo type. type ICalc = interface function Multiplicar
(const x,y : integer) : integer; end; Note que no podemos apertar
Shift+Ctrl+C e implementar o mtodo Multiplicar. Esse um mtodo de
interface, como um mtodo abstract de uma classe.Logo abaixo da
interface declare a seguinte classe: TComputador = class
(TInterfacedObject,ICalc) function Multiplicar (const x,y :
integer) : integer;end; TCalculadora = class
(TInterfacedObject,ICalc) function Multiplicar (const x,y :
integer) : integer;end; TComputador = class
(TInterfacedObject,ICalc) significa TComputador herda de
TInterfacedObject e implementa a interface ICalc. Isso no herana
mltipla. Observe que ambas as classes TComputador e TCalculadora
implementam a interface ICalc, e descendem de TInterfacedObject.
TInterfacedObject se encarrega de implementar a interface
-
IInterface.Aperte Shift+Ctrl+C para declarar os cabealhos dos
mtodos. function TComputador.Multiplicar(const x, y: integer):
integer;begin result:=x*y;end; function
TCalculadora.Multiplicar(const x, y: integer): integer;var i :
integer;begin result:=0; if (x0) and (y0) then for i:=1 to abs(y)
do result:=result+x;end; Observe que ambas as classes implementam o
mtodo Multiplicar de ICalc, porm de formas diferentes. Usando
Edits, um RadioGroup e um Button construa o seguinte formulrio:
Figura 2.D um duplo clique no boto e digite: procedure
TFrmMain.Button1Click(Sender: TObject);var Obj : ICalc; n1,n2,r :
integer;begin if RadioGroup1.ItemIndex=-1 then exit; case
RadioGroup1.ItemIndex of 0 : Obj:=TCalculadora.create; 1 :
Obj:=TComputador.create; end; n1:=StrToInt(Edit1.Text);
n2:=StrToInt(Edit2.Text); r:=Obj.Multiplicar(n1,n2);
Edit3.Text:=IntToStr(r);end; Rode e teste a aplicao.
-
Figura 3.
Parte XX Objetos COM
Neste artigo, veremos como criar e como usar objetos COM, um dos
fundamentos mais bsicos da programao de objetos distribudos e
DataSnap.COM (Component Object Model) a tecnologia desenhada pela
Microsoft que possibilita a comunicao entre aplicaes clientes e
aplicaes servidoras. Essa comunicao feita atravs do que chamamos de
interfaces. Uma interface COM a maneira como um objeto expe sua
funcionalidade ao meio externo. Um GUID (Globally Unique
Identifier) um nmero utilizado no COM para identificar uma
interface ou uma Co-Class. Quando utilizado para identificar uma
interface, um GUID tambm chamado de IID (interface ID).Vamos ento
criar nosso primeiro objeto COM. Siga os seguintes passos: Clique
em File|New|Other. Na guia ActiveX clique em ActiveX Library. Uma
nova biblioteca ser criada, que ser a DLL que conter nosso objeto
COM. Salve esta biblioteca com o nome de LibExemplo.Clique
novamente em File|New|Other. Na guia ActiveX escolha agora COM
Object.
Figura 1.Na caixa de dilogo que aparece, digite Soma para o nome
da classe. O Delphi automaticamente preenche o nome da interface a
ser implementada (ISoma). Deixe a opo
-
Multiple Instance como padro para Instancing (isso far com que
uma nova instncia de nosso objeto seja criada para cada aplicao
cliente). Na opo Threading Model deixe o padro Apartment (cada
objeto COM executado dentro de seu prprio Thread).
Figura 2.Clique em OK. Aparecer a Type Library do objeto COM.
Salve a unidade criada com o nome de uSoma. Uma Type Library
constitui a maneira de identificarmos os mtodos suportados por uma
interface. O Delphi oferece um editor onde se pode facilmente
construir uma interface para um objeto e atravs de cdigo Pascal
implementar essa interface. Para visualizar o editor da Type
Library de um objeto voc pode acessar o menu View|Type Library.No
editor da Type Library, d um clique de direita sobre a interface
ISoma. Escolha New|Method. D o nome de Somar para o mtodo. Em nosso
primeiro exemplo, criaremos um funo que receber dois parmetro
Single, retornando a soma de ambos.Na opo Return Type da guia
Parameters, escolha Single. Clique em Add e insira dois parmetros
(Num1 e Num2) do tipo Single. Salve tudo para o Delphi atualizar a
unit de implementao. Tenha em mente que OLE e COM no oferecem
suporte a todos os tipos de dados do Delphi.
-
Figura 3.Clique agora em View|Units e escolha LibExemplo_TLB.
Voc ver uma extensa unidade Pascal que define a Type Library da
interface criada. Logo no incio h uma declarao avisando a voc para
no mudar este cdigo fonte. Qualquer modificao neste cdigo deve ser
feita por meio do editor da Type Library.Procure pela seguinte
declarao: ISoma = interface(IUnknown)
['{F1431B15-D645-4BC1-8F26-81B7BBF8D4C7}'] function Somar(Num1:
Single; Num2: Single): single; stdcall;end; Esta a definio da nossa
interface feita anteriormente no editor da Type Library. O que
precisamos fazer agora codificar o mtodo Somar. Abra a unit uSoma e
na seo implementation implemente a funo (o Delphi j colocou os
cabealhos). function TSoma.Somar(Num1, Num2: Single): Single;begin
result:=Num1+Num2;end; Agora basta compilar a biblioteca. Clique em
Project|Build LibExemplo. Clique em Run|Register ActiveX Server
para registrar o objeto.Vamos agora criar um cliente para nosso
objeto COM criado anteriormente. Siga os passos abaixo: Clique em
File|New Application. Salve a unit com o nome de uClienteCOM.pas e
o projeto com o nome de ClienteCOM.dpr. D o nome de FrmMain e
Caption Objetos COM ao formulrio. Coloque um boto no formulrio, com
o Caption Somar, e trs Edits. Veja a figura:
-
Figura 4.O objetivo agora clicar no boto e chamar a funo Somar
de nosso objeto COM, definido por nossa interface ISoma,
implementada em nossa Co-Class TSoma. Para isso precisamos importar
a Type Library do objeto que queremos instanciar. Clique em
Project|Add to Project e localize o arquivo LibExemploLib_TLB. Isso
faz com que a Type Library da interface ISoma seja incorporada ao
nosso aplicativo. Mas veja bem, apenas a interface do objeto ser
conhecida por ns, pois a sua implementao ficar oculta. Ns saberemos
que ISoma possui o mtodo Somar mas no sabemos como ela realiza o
processamento internamente.Agora no formulrio principal clique em
File|Use Unit e escolha LibExemplo_TLB. Faa o seguinte no evento
OnClick do boto: procedure TFrmMain.Button1Click(Sender:
TObject);var Obj : ISoma; n1,n2,n3 : single;begin
Obj:=CoSoma.Create; n1:=StrToInt(Edit1.Text);
n2:=StrToInt(Edit2.Text); n3:=Obj.Somar(n1,n2);
Edit3.text:=FloatToStr(n3);end; Execute e veja o resultado como na
figura abaixo:
Figura 5.Caso o servidor no tenha sido registrado uma exceo do
tipo EOleSysError levantada com a mensagem Classe no
registrada.
Parte XXI Servidores de Aplicao e Remote Data Modules
Utilizamos o DataModule para separar o cdigo de acesso a dados
da interface de usurio. no DM que utilizamos os TDataSets, como a
TSQLQuery e a TSQLTable, alm do componente TSQLConnection. Esses
componentes so responsveis pela comunicao com o banco de dados
(SGBDR). No DM geralmente tambm so implementados procedures que
fazem validao sobre alguns dados, introduzem um campo padro ou um
campo calculado. Quando compilamos nossa aplicao, todo o cdigo de
acesso, clculos e regras ficaro residentes em nosso executvel. Se
por algum motivo for necessrio mudar algum parmetro de consulta,
alguma instruo SQL, um campo TField, uma mscara, alguma regra de
dados, precisaremos recompilar toda a aplicao. E isso no tudo: os
componentes dbExpress usam bibliotecas .dll que devem ser colocadas
em todas as mquinas da rede que acessar o banco. Tambm devemos
instalar e configurar os conhecidos SQL Clients (clientes de banco
SQL), que so
-
bibliotecas especficas para cada SGBDR. Uma soluo separar o
acesso em um Remote DataModule. O Remote DataModule nada mais do
que um servidor COM, que basicamente tem a mesma funo de um DM,
porm pode fazer uso dos recursos do DCOM. A grande vantagem de se
usar um RDM que ele pode ficar totalmente separado do executvel
principal da aplicao cliente, residindo em um outro processo, em um
outro computador. Surge ento mais uma camada em nosso sistema.
Temos a o que se chama de uma aplicao 3 camadas : o cliente, o RDM
(constituindo o servidor de aplicao) e o servidor de dados (SGBDR).
As aplicaes desenvolvidas utilizando esse tipo de tecnologia tambm
so conhecidas como aplicaes MultiTier (Multicamadas).
Figura1. Remote DataModuleAqueles procedures que colocamos em
nosso antigo DM agora sero implementados dentro de nosso servidor
DCOM (o RDM). O cliente conhecendo a interface do nosso objeto DCOM
pode ento fazer chamadas a esses procedimentos residentes no RDM,
atravs de uma RPC (Remote Procedure Call) usada pelo DCOM. O
mecanismo que possibilita que clientes faam chamadas a funes de
objetos residindo em um diferente processo ou em uma diferente
mquina se chama marshaling.O RDM ser responsvel por gerenciar o
acesso aos dados e as regras de negcio chamadas Business Rules
(todas as validaes, imposies, clculos, constraints e acessos que
envolvem os dados de uma aplicao). Os componentes de acesso do
Delphi (BDE, ADO ou IBX) precisam estar somente no servidor de
aplicao. As bibliotecas de clientes SQL tambm no sero necessrias em
cada mquina cliente, somente no servidor de aplicao. O cliente s
precisa levar consigo a biblioteca dbclient.dll se for feito em
Delphi 4 e Midas.dll se feito em Delphi 5 ou superior. Todas as
aplicaes clientes acessam a mesma camada (middle- tier), evitando
redundncia de regras de negcio, que ficam encapsuladas em uma nica
camada compartilhada. As aplicaes clientes ento contm no muito mais
do que a interface com o usurio. Para o usurio final uma aplicao
MultiTier no muito diferente de uma aplicao tradicional
cliente-servidor. Os clientes passam a fazer apenas alguma
manipulao de tela e alguma chamadas ao servidor de aplicao (por
esse motivo so chamados Thin Clients). Os clientes NUNCA se
comunicam diretamente com o servidor de banco de dados.
Criando um Servidor de Aplicao DCOM
Comece uma nova aplicao em Delphi clicando em
File|New|Application. Salve a unit com o nome de uFrmMain.pas e o
projeto como AppServerDCOM.dpr. D o nome de FrmMain ao formulrio,
caption de Servidor DCOM Ativado e reduza o seu tamanho. Coloque-o
no canto inferior direito da tela. Este formulrio apenas indicar
que o servidor DCOM est ativado.
Figura. Formulrio principal do servidor DCOMAgora clique em
File|New|Other. No Object Repository, na guia MultiTier, escolha
Remote DataModule.
-
Figura. Criando um Remote DataModuleNa caixa de dilogo que
aparece, digite RDM para o nome da Co-Class. Deixe as opes
Instancing e Threading Model como esto (estas so as mesmas opes
disponveis quando criamos um objeto COM no primeiro exemplo). Salve
a unit criada como uRDM.pas.Coloque um componente SQLConnection e
configure o acesso ao banco EMPLOYEE.GDB do Interbase, normalmente
situado em C:\Arquivos de programas\Arquivos Comuns\Borland
Shared\data\employee.gdb. Defina seu LoginPrompt como False e
Connected como True.
Figura. Conectando ao Interbase a partir do Remote
DataModule
Coloque um SQLDataSet apontando para o SQLConnection e na sua
propriedade CommandText digite: select * from CUSTOMER Coloque no
RDM o componente TDataSetProvider (paleta DataAccess). Aponte sua
propriedade DataSet para SQLDataSet. Voc deve usar um componente
TDataSetProvider para cada TDataSet que utilizar no RDM.Vamos tambm
incluir um procedure em nosso servidor, que ser chamado remotamente
pelo cliente, que ter a funo de verificar se um determinado CPF
vlido (note que a validao - uma regra de negcio - ficar totalmente
separada do cliente, e se ela mudar, no precisaremos recompilar ou
reinstalar clientes, alm do processamento de clculo da regra ser
totalmente feito no servidor de aplicao). Importante: no confunda
isso com um Stored Procedure dos servidores SQL que algo totalmente
diferente.Abra o editor da Type Library clicando em View|Type
Library. D um clique de