Вы находитесь на странице: 1из 71

Delphi e banco de dados orientado a objetos

Introduo
Este um artigo prtico, estamos preocupados em demonstrar a utilizao do Delphi para acessar objetos em um banco de dados orientado a objetos. No nos deteremos em discusses filosficas sobre vantagens ou desvantagens desta ou daquela tecnologia, no tencionamos evangelizar ningum, apenas, tentamos prestar um servio queles desenvolvedores que j utilizam o paradigma de POO e, no entanto acabam persistindo seus objetos em um banco de dados relacional por costume ou por terem dificuldade em utilizar a persistncia em um SGBD OO, j que ainda h a carncia de material didtico ensinando e exemplificando seu uso. Tambm no tentaremos ensinar a teoria que envolve um banco de dados OO, nem mesmo os detalhes e artimanhas prprios desta tecnologia, j que isto renderia um artigo maior que este. Para os interessados neste assunto, sugiro a leitura da documentao que acompanha o banco de dados que usaremos como exemplo que de excelente qualidade. O banco de dados escolhido o Cach da empresa Intersystems, vrios quesitos influenciaram na escolha, dentre eles podemos citar a facilidade de download e instalao, a excelente documentao que o acompanha, a riqueza de sua linguagem prpria, a forma como so criados os objetos e principalmente o fato de que ele realmente orientado a objetos, no h mapeamento objeto-relacional ou vice-versa no acesso aos dados.

Parte 1 - Download e instalao do Cach


Para fazer o download do Cach v ao site do fabricante em www.intersystems.com e localize a pgina de download (http://download.intersystems.com/download/ no momento em que este texto foi escrito). Nesta pgina voc encontrar um link para efetuar seu registro junto ao fabricante, aps preencher o registro voc estar apto a fazer o download de uma verso totalmente funcional do Cach.

Na pgina seguinte clique no checkbox se voc concorda com os termos da licena e escolha o produto e o tipo de download desejado (sugiro offline installation, desta forma voc ter o executvel para instalar em outras mquinas e at para futuras reinstalaes).

Clique no boto download e ser apresentada a tela com as informaes do arquivo.

Aps o download estar completo d um duplo clique no arquivo CachePCkit.exe, isto lanar o descompactador. Escolha uma pasta onde os arquivos sero descompactados e clique em Next.

Aps a descompactao ser questionado o idioma para instalao, e a boa surpresa que est disponvel o Portugus (Brasil).

Clique em OK para prosseguir com a instalao. Logo voc ser solicitado a concordar com os termos da licena, caso concorde clique em Sim para prosseguir.

Escolha a pasta onde o Cach ser instalado e clique em OK.

Ser solicitada uma confirmao para a criao da pasta escolhida, clique em Sim para prosseguir.

Neste momento ser apresentada uma tela questionando se deseja instalar o sistema com suporte a UNICODE (16 bits) ou no, caso no use caracteres de duplo byte (alguns idiomas especficos como o Japons, por exemplo) escolha a opo padro de 8-bit.

Ser apresentada uma tela com as informaes fornecidas para conferncia, caso tudo esteja de acordo clique em Avanar para prosseguir.

Neste ponto, caso o IIS esteja em execuo no sistema, o instalador solicitar permisso para par-lo para que ele possa instalar o gateway CPS (o Cach tem um recurso fantstico para criao de paginas da WEB). Sugiro que voc clique em Sim para que o instalador possa fazer seu trabalho. A partir da o instalador entrar nas telas de progresso.

Caso nenhum problema ocorra ser apresentada a tela de sucesso perguntando se voc deseja exibir o Getting Started (adianta eu recomendar que voc leia o manual antes de comear a usar o software?), clique em Terminar.

Neste momento o Cach j est instalado e executando em sua mquina, observe o cone na bandeja do sistema que d acesso s vrias funcionalidades do sistema.

Como o instalador no se preocupa em apagar os arquivos temporrios da instalao, sugiro que voc apague a pasta C:\CacheKit e todo seu contedo. No se preocupe com o IIS, pois o instalador se encarregar de coloc-lo no ar novamente.

Parte 2 - Criando as classes no banco


Para criar as classes no Cach inicie o Studio, isso pode ser feito pelo grupo de programas ou clicando no cone do Cach na barra de tarefas.

ou

O Studio ser iniciado. O Cach trabalha com classes dentro de pacotes, pacotes dentro de projetos e projetos dentro de namespaces, porm, no necessrio criar estes elementos nesta ordem j que o wizard nos possibilitar a criao simultnea. Supondo que voc acabou de instalar o Cach, o namespace padro ser (LOCALTCP:USER) e o projeto Default__system, caso no seja, alterne para este namespace atravs do menu Arquivo | Change Namespace...

Observe se voc est trabalhando neste namespace

Neste namespace criaremos nosso projeto, nosso pacote e nossas classes. Clique em Arquivo | Novo Projeto Clique em Arquivo | Salvar Projeto, salve como Exemplo, observe o novo projeto

Clique com o boto direito do mouse (CD daqui em diante) sobre Classes e selecione Criar Nova Classe

Na janela que se abre informe o nome do pacote (Exemplo em nosso caso), o nome da classe (Telefone) e se desejar, uma descrio tambm.

Clique Next (em algumas instalaes no aparece o boto Next, se for este o caso, clique em Concluir, isto pode acontecer em outros casos, esteja avisado, daqui pra frente se no vir o boto Next clique em Concluir), Na janela que se abre selecione Serial (isto quer dizer que as instancias de Telefone sero embutidas em outras classes como Cliente, Empregado, Fornecedor, etc.), clique em Next

Vamos fazer uma breve parada para entender as opes desta tela. Sugiro uma consulta documentao do Cach, mas vamos a uma breve descrio: Persistente: opo para objetos que devem ter seus atributos persistidos no banco; Serial: cria objetos que podem ser embutidos em outros objetos permitindo assim a criao de tipos complexos; Registrada: objetos registrados, mas que no sero gravados no banco; Abstrata: classe utilizada para a derivao de novas classes, no devero existir objetos deste tipo; Tipo de Dados: cria uma definio de tipo; CSP: objetos que respondero a eventos http; Derivada: classe que herda de outra classe, usada em conjunto com a propriedade a seguir; Nome da superclasse: caso a classe seja derivada, informamos nesta janela o nome da classe ancestral (clicando no boto procurar ser aberta uma janela com as classes existentes para que uma seja selecionada, veja a figura).

Reiterando, neste caso selecione Serial e Clique Next A janela que se abre questiona se a classe possui suporte a XML e se a classe suportar populao automtica de dados (neste caso ser criado um mtodo que insere valores aleatrios nos objetos para teste). Deixe tudo como est (no marque nada) e clique em Finish.

Neste ponto a classe foi criada e o arquivo aberto na rea principal do programa. A seguir a definio da classe: Class Exemplo.Telefone Extends %SerialObject [ ClassType = serial, ProcedureBlock ] { } Precisamos agora inserir os atributos desta classe. Isto pode ser feito tanto diretamente no cdigo, quanto utilizando wizards. Antes de prosseguir vamos dar uma olhada no modelo que pretendemos implementar:

Como visto, a classe Telefone ter trs atributos do tipo String (Numero, Ramal e Prefixo) e a classe Cliente ter trs atributos do tipo String (Nome, Sobrenome e CPF). Para inserir as propriedades na classe Telefone certifique-se de que o arquivo aberto no editor o arquivo que contem a classe Telefone.

Podemos usar o menu Classe, ou os speedbuttons da barra de tarefas para inserir as propriedades:

ou O wizard ser iniciado e na primeira tela informaremos o nome da propriedade

Clique Next Nesta tela podemos escolher o tipo da propriedade, selecione Um nico valor do tipo: e escolha %String, caso deseje outro tipo clique no boto Procurar

Existem outros tipos mais complexos disposio, sugiro uma consulta na documentao para entendimento de todas as opes. Clique Next Na janela que se abre podemos ajustar parmetros para especificar se a propriedade Obrigatria (NOT NULL), Indexada, Chave, ou calculada, podemos ainda dar um nome opcional para ser exibido no acesso usando SQL. Deixe tudo conforme o padro e Clique Next;

Na janela que se abre podemos ajustar diversos parmetros da propriedade, mais uma vez aceitaremos o padro, Clique Next

Na janela que se abre podemos escolher sobrescrever os mtodos Get e Set da propriedade. Estes mtodos no devem ser novidade para os Analistas, j que um padro da Engenharia de Software, e nem para programadores Delphi, j que o Delphi segue este padro quando criamos properties em nossas classes; de qualquer forma, uma rpida explicao: para respeitar o encapsulamento (principio de information hide neste caso), todos os campos de uma classe so privados ou protegidos e fornecemos mtodos chamados Getters para leitura e Setters para escrita, alem de ocultar a implementao (black box) estes mtodos permitem, por exemplo, que se faa alguma validao antes de ler ou escrever no campo especifico. Em nosso exemplo no desejamos sobrescrever os mtodos. Clique Finish.

A propriedade foi criada e nosso cdigo deve estar como a seguir: Class Exemplo.Telefone Extends %SerialObject [ ClassType = serial, ProcedureBlock ] { Property Numero As %String; } Siga em frente e crie as outras duas propriedades (Ramal e Prefixo). Definio final da classe Telefone:

Class Exemplo.Telefone Extends %SerialObject [ ClassType = serial, ProcedureBlock ] { Property Numero As %String; Property Prefixo As %String; Property Ramal As %String; } Salve o projeto. Para ter certeza de que tudo est bem precisamos compilar a classe (ou diretamente o pacote, o que implica em compilar todas as classes dentro dele). Para isso, CD no nome do pacote e selecione a opo Compilar Pacote Exemplo. Obs. Existem duas janelas que exibem informaes muito parecidas no Workspace, uma a aba Projeto, outra a aba Namespace, para compilar o pacote necessrio que voc esteja na aba Projeto.

Se tudo estiver bem, ser dada mensagem de sucesso e apresentada uma tela sugerindo a releitura do arquivo, aceite clicando em Yes

Vamos agora criar a classe Cliente, siga em frente e crie a classe (esta classe dever ser do tipo Persistent e no Serial como Telefone), depois adicione as propriedades, Nome, Sobrenome e CPF. Sua nova classe dever estar como a seguir: Class Exemplo.Cliente Extends %Persistent [ ClassType = persistent, ProcedureBlock ] { Property CPF As %String; Property Nome As %String; Property Sobrenome As %String; }

Inserindo a propriedade Telefone em Cliente


O prximo passo inserir uma ou mais propriedades do tipo Telefone na classe Cliente. Adicione uma nova propriedade, informe TelResidencial no nome e clique Next

Em Um nico valor do tipo: clique no boto Procurar, expanda o n Other depois expanda o pacote Exemplo e selecione Telefone

Clique Finish Salve tudo e compile o pacote.

Cdigo final das classes sem os comentrios: Class Exemplo.Cliente Extends %Persistent [ ClassType = persistent, ProcedureBlock ] { Property CPF As %String; Property Nome As %String; Property Sobrenome As %String; Property TelResidencial As Telefone; }

Class Exemplo.Telefone Extends %SerialObject [ ClassType = serial, ProcedureBlock ] { Property Numero As %String; Property Prefixo As %String; Property Ramal As %String; } Obs: neste momento estamos trabalhando em um wizard para ser instalado na IDE do Delphi que permitir a exportao das classes contidas em uma unit para arquivos xml no formato do Cach para que as classes sejam importadas no Cach, sem necessidade deste processo manual de criao, brevemente liberaremos uma verso beta deste wizard.

Parte 3 Criando a aplicao Cliente no Delphi 2005


Na verdade, no criaremos uma aplicao cliente, mas sim, trs (ou mais...). Demonstraremos como acessar a parte relacional via ODBC em Win32 e .NET e tambm como acessar diretamente os objetos usando COM. Quando criamos nossas classes, o prprio Cach se encarrega de criar a parte relacional, ou seja, uma tabela para cada classe Persistent que definimos (neste caso somente a classe Cliente estar acessvel via SQL, j que Telefone Serial, e no, Persistent). Como o Delphi no possui drivers especficos para acessar o Cach, usaremos ODBC.

3a - Cliente Win32
Crie uma nova aplicao Win32, adicione e configure os componentes como a seguir: Componente Propriedade Valor ADOTable Name ADOtbCliente ConnectionString Veja abaixo TableName Veja abaixo DataSource DataSet ADOtbCliente DBGrid DataSource DataSource1 Align alClient DBNavigator DataSource DataSource1 Align alBottom Vamos configurar a propriedade ConnectionString da ADOTable, para isso, usaremos o assistente. Selecione a propriedade ConnectionString no Object Inspector (OI daqui em diante), clique no boto com as reticncias que surge ao lado do nome da property para abrir o assistente, clique no boto Build... (Criar...) Na janela que se abre selecione a aba Connection (Conexo), expanda o ComboBox da opo Use data source name (Usar o nome da fonte de dados) e selecione CACHEWEB User (supondo que voc criou o pacote no namespace USER como foi recomendado)

Clique em OK nesta tela e na seguinte. Selecione a propriedade TableName da ADOTable, na lista dever aparecer apenas a tabela Cliente, ai existe um pequeno truque, no adianta selecionar a tabela, ao tentar ativar o componente voc obter uma mensagem de erro

necessrio informar o pacote no qual a tabela est inserida, portanto, na propriedade TableName digite Exemplo.Cliente, agora sim voc pode ativar o componente

E isso tudo, execute seu programa e observe que o Cach j expandiu a propriedade TelResidencial exibindo todos os seus campos, insira alguns clientes na tabela para testar sua aplicao. No se impressione com o fato de todos os ID aparecerem como 0, o Cach atribuir nmeros seqenciais corretos ao campo ID. Projetos\Exemplo1.zip

3b - Cliente .NET Windows Forms


Crie uma aplicao Windows Forms para Delphi, adicione um DataGrid ao form e configure sua propriedade Dock como Fill. Por padro o Delphi no instala os componentes ODBC, vamos ento fazer isso agora.

Clique no menu Components | Installed .NET Components... Na janela que se abre procure por OdbcConnection, marque o CheckBox ao lado dele; Prximo a este componente voc dever encontrar o OdbcDataAdapter, marque o CheckBox correspondente e clique em OK

Os componentes foram instalados e esto na paleta Data Components, adicione um de cada ao form. Selecione o OdbcConnection e selecione ConnectionString, clique nas reticncias para abrir o assistente. Aqui h outro truque, no podemos configurar a string como fizemos na aplicao Win32, selecione a aba Connection (Conexo), marque a opo Use connection string (Usar a seqncia de conexo) e clique no boto Build... (Criar...)

Na janela que se abre selecione a aba Machine Data Source (Fontes de dados de mquina) e ai sim, selecione CACHEWEB User

Clique em OK nas duas telas para fechar o assistente. Selecione o OdbcDataAdapter , expanda a propriedade SelectCommand, em Connection selecione o OdbcConnection e em CommandText digite select * from Exemplo.Cliente; Adicione um componente DataSet ao form; Vamos agora inserir o seguinte cdigo no evento Create do form: constructor TWinForm.Create; begin inherited Create; // // Required for Windows Form Designer support // InitializeComponent; // // TODO: Add any constructor code after InitializeComponent call // OdbcConnection1.Open; OdbcDataAdapter1.Fill(DataSet1, 'Cliente'); DataGrid1.DataSource:= DataSet1.Tables['Cliente']; end; Isso tudo, execute a aplicao.

Projetos\Exemplo2.zip

Parte 4 - Acessando os objetos usando COM


Ok Barbieri, muito bom, mas at agora s vi acesso a BD relacional... Para usar relacional eu uso o Interbase que um BD espetacular! Onde esto os objetos? Vamos construir agora a aplicao que acessa diretamente os objetos do Cach usando COM. para isso, crie uma nova aplicao Win32 e monte o form como a figura a seguir:

Para podermos acessar o servidor COM do Cach vamos importar a Type Library dele. Menu Component | Import Component, selecione Import a Type Library e clique em Next

Em Description localize CacheObject, selecione-o e clique Next

Na janela que se abre em Unit dir Name, aponte para a pasta do seu projeto ou para uma pasta que esteja no Browsing Path do Delphi, clique Next, na janela seguinte, deixe marcada a opo Create Unit e clique em Finish. O Delphi importou a Type Library e criou uma unit (CacheObject_TLB.pas) para ela. Precisamos adicionar esta unit clusula uses de nosso form, adicione no uses da Interface j que usaremos um tipo contido nesta unit ainda na declarao da classe do nosso form. Adicione tambm a unit ComObj clusula uses (Interface ou Implementation). Sugiro que voc d uma boa olhada nesta unit para se familiarizar com as interfaces e mtodos exportados pela TLB. Adicione um campo chamado Fabrica do tipo IFactory na seo private da classe do form type TForm1 = class(TForm) btConectar: TButton; btLocalizar: TButton; edtID: TEdit; Label2: TLabel; private { Private declarations } Fabrica: IFactory; public { Public declarations }

end; Vamos agora programar o manipulador de evento do click do boto btConectar procedure TForm1.btConectarClick(Sender: TObject); begin Fabrica:= CreateComObject(CLASS_Factory) as IFactory; if not Fabrica.Connect('cn_iptcp:127.0.0.1[1972]:USER') then ShowMessage('No foi possvel estabelecer conexo'); end; Na primeira linha ns nos conectamos ao servidor COM e podemos a partir de agora invocar os mtodos declarados na interface IFactory. Na segunda linha invocamos o mtodo Connect passando para ele uma string que contem o socket da conexo mais a base com a qual desejamos estabelecer a conexo. Neste momento j estamos conectados e com acesso a base User do Cach, podemos agora acessar seus objetos. No click do boto btLocalizar invocaremos o mtodo OpenID que recebe como parmetro o nome da classe, o ID do objeto e o tipo de concorrncia que ser usado nesta consulta (0: sem bloqueio, 1: atmico, 2: compartilhado, 3: compartilhado, mas retido, 4: acesso exclusivo, veja a documentao para mais detalhes). procedure TForm1.btLocalizarClick(Sender: TObject); var Cliente: Variant; begin try Cliente:= Fabrica.OpenId('Exemplo.Cliente', edtID.Text, 1); except on E: Exception do ShowMessage(E.Message); end; if VarIsNull(Cliente) or VarIsClear(Cliente) then Exit; ShowMessage('Nome: ' + Cliente.Nome + #13 + 'Sobrenome: ' + Cliente.Sobrenome + #13 + 'CPF: ' + Cliente.CPF + #13 + 'TelRes Numero: ' + Cliente.TelResidencial.Numero + #13 + 'TelRes Ramal: ' + Cliente.TelResidencial.Ramal + #13 + 'TelRes Prefixo: ' + Cliente.TelResidencial.Prefixo); end;

pronto, isto tudo, execute o programa, clique em Conectar, depois, digite um ID valido no edit e clique em Localizar ID.

Projetos\Exemplo3.zip

Parte 5 Criando uma Query na classe


Como as classes no Cach so realmente classes e no meras tabelas ou deposito de dados, podemos inserir mtodos nestas classes, mas como as classes no Cach no so meras classes, mas tambm poderosas tabelas de banco de dados (desculpem, mas no resisti). Vamos inserir uma query que receber o nome do cliente como parmetro e retornar seus dados. Inicie o Studio, abra o projeto Exemplo, abra a classe cliente e adicione uma query como descrito a seguir: Query PorNome(oNome As %String = "") As %SQLQuery(CONTAINID = 1, ROWSPEC = "ID:%Integer,Nome,Sobrenome,CPF,Tel_Num,Tel_Pref,Tel_Ramal", SELECTMODE = "RUNTIME") { SELECT ID, Nome, Sobrenome, CPF, TelResidencial_Numero, TelResidencial_Prefixo, TelResidencial_Ramal FROM Exemplo.Cliente WHERE (Nome %STARTSWITH :oNome) ORDER BY Nome } A seguir a definio completa da classe Cliente: Class Exemplo.Cliente Extends %Persistent [ ClassType = persistent, ProcedureBlock ] { Property CPF As %String; Property Nome As %String; Property Sobrenome As %String; Property TelResidencial As Telefone; Query PorNome(oNome As %String = "") As %SQLQuery(CONTAINID = 1, ROWSPEC = "ID:%Integer,Nome,Sobrenome,CPF,Tel_Num,Tel_Pref,Tel_Ramal", SELECTMODE = "RUNTIME") { SELECT ID, Nome, Sobrenome, CPF, TelResidencial_Numero, TelResidencial_Prefixo, TelResidencial_Ramal FROM Exemplo.Cliente WHERE (Nome %STARTSWITH :oNome) ORDER BY Nome } } No esquea de compilar o pacote antes de prosseguir!

Agora iremos montar uma aplicao mais sofisticada utilizando um pouco mais dos recursos do Cach (e isso s a ponta do iceberg). Neste exemplo alm da consulta (que j foi demonstrada no exemplo anterior), iremos utilizar uma query e fazer a insero de um novo cliente. Veja na figura o form de nossa aplicao (Win32):

Componente Button Button Button Button Edit Edit Edit Edit Edit Edit Edit Edit ListBox GroupBox

Propriedade Name Caption Name Caption Name Caption Name Caption Name Name Name Name Name Name Name Name Name Name

Valor btTrocarBase Trocar Base btLocID Localizar btLocNomeParte Localizar btInserir Inserir edtLocID edtLocNomeParte edtNome edtSobrenome edtCPF edtTelPrefixo edtTelNumero edtTelRamal ListBox1 GroupBox1

GroupBox 8 Label

Caption Name Caption Caption

Cliente GroupBox2 Telefone Residencial Localizar Cliente com ID = Parte do Nome: Nome: Sobrenome: CPF: Prefixo: Nmero: Ramal:

Uma breve observao sobre a metodologia utilizada: Neste exemplo utilizamos programao procedural com dois intuitos, o primeiro o de no desviar o foco de ateno da utilizao dos recursos do Cach para os da POO, o segundo no aumentar ainda mais este texto, porm, em uma parte futura deste artigo criaremos um exemplo usando POO, o fonte j est disponvel para download junto aos demais exemplos ( Projetos\Exemplo7.zip ). Vamos ento ao cdigo da nossa aplicao. Caso voc tenha colocado a unit com a importao da Type Library em uma pasta que esteja no Browsing Path do Delphi, voc s precisa adicion-la clusula uses da unit do form (uses da interface), caso contrrio, copie a unit para dentro da pasta do seu projeto atual (CacheObject_TLB.pas). Na seo private do form declare um campo fabrica do tipo IFactory: private { Private declarations } Fabrica: IFactory; public { Public declarations } end; Adicione a unit ComObj clusula uses (interface ou implementation). No evento OnCreate do form colocaremos o cdigo que estabelece a conexo ao banco e base de dados: procedure TForm1.FormCreate(Sender: TObject); begin Fabrica:= CreateComObject(CLASS_Factory) as IFactory; if not Fabrica.Connect('cn_iptcp:127.0.0.1[1972]:USER') then ShowMessage('No foi possvel estabelecer conexo'); end;

No click do boto btTrocarBase demonstramos como exibir uma janela para que o usurio possa se conectar a outra base de dados: procedure TForm1.btTrocarBaseClick(Sender: TObject); begin Fabrica.Connect(Fabrica.ConnectDlg('Conectar-se a:')); end; No click do boto btLocID exibimos em um ShowMessage os dados do cliente que possui o ID informado no edtLocID: procedure TForm1.btLocIDClick(Sender: TObject); var Cliente: Variant; begin try Cliente:= Fabrica.OpenId('Exemplo.Cliente', edtLocID.Text, 1); except on E: Exception do ShowMessage(E.Message); end; if VarIsNull(Cliente) or VarIsClear(Cliente) then Exit; ShowMessage('Nome: ' + Cliente.Nome + #13 + 'Sobrenome: ' + Cliente.Sobrenome + #13 + 'CPF: ' + Cliente.CPF + #13 + 'TelRes Numero: ' + Cliente.TelResidencial.Numero + #13 + 'TelRes Ramal: ' + Cliente.TelResidencial.Ramal + #13 + 'TelRes Prefixo: ' + Cliente.TelResidencial.Prefixo); end; No click do boto btLocNomeParte fazemos a chamada query que criamos no Cach, esta chamada retornar um ResultSet com os dados do(s) Cliente(s) cujo nome inicie com as letras digitadas no edtLocNomeParte, usaremos o ListBox1 para exibir estes dados. procedure TForm1.btLocNomeParteClick(Sender: TObject); var ResultSet: IResultSet; i: Integer; s: string; begin ListBox1.Clear; ResultSet:= Fabrica.ResultSet('Exemplo.Cliente', 'PorNome') as IResultSet; Variant(ResultSet).Execute(edtLocNomeParte.Text);

while ResultSet.Next do begin s:= ''; for i:= 1 to ResultSet.GetColumnCount do s:= s + ' ' + ResultSet.GetColumnName(i) + ': ' + ResultSet.GetDataAsString(i); ListBox1.Items.Add(s); end; ResultSet.Close; end; Neste cdigo trabalhamos com um campo Fabrica que do tipo IFactory e representa um objeto do tipo Factory do Cach, e tambm com uma varivel ResultSet do tipo IResultSet que representa um objeto do tipo ResultSet do Cach (sugiro uma consulta a essas duas classes na documentao), como a fabrica j esta conectada, chamamos o mtodo ResultSet informando o objeto e o nome da query que desejamos acessar, o resultado ser colocado na var ResultSet, agora, podemos chamar o mtodo Execute do ResultSet que recebe como parmetro o contedo do edtLocNomeParte, esta consulta retornar todos os clientes cadastrados cujo nome inicie com as letras passadas como parmetro, caso deixemos o edit em branco sero retornados todos os clientes. De posse dos dados podemos percorr-los usando Next. GetColumnCount retorna o nmero de colunas contido em cada linha do resultado e ResultSet.GetColumnName retorna o nome de cada coluna, GetDataAsString que realmente traz os dados ( j os traz convertidos em string), tudo isso est sendo gravado em uma string e aps obtermos todas as informaes para cada linha, inserimos o contedo da string no ListBox. Para inserir um novo objeto no banco ainda mais fcil, observe o cdigo do boto btInserir: procedure TForm1.btInserirClick(Sender: TObject); var Cliente: Variant; begin //cria um novo Cliente:= Fabrica.New('Exemplo.Cliente', True); Cliente.Nome:= edtNome.Text; Cliente.Sobrenome:= edtSobrenome.Text; Cliente.CPF:= edtCPF.Text; Cliente.TelResidencial.Numero:= edtTelNumero.Text; Cliente.TelResidencial.Prefixo:= edtTelPrefixo.Text; Cliente.TelResidencial.Ramal:= edtTelRamal.Text;

Cliente.Sys_Save; Cliente.Sys_Close; Cliente:= NULL; end; Chamamos o mtodo New de Factory para obter uma nova instncia, a partir da s passar os valores para as propriedades, por fim salvamos as informaes, e isso tudo, tudo muito simples e fcil de usar. Veja imagens do programa em execuo:

Projetos\Exemplo4.zip Bem, promessa cumprida, ai esto as trs aplicaes acessando os objetos do Cach. Porm, como um bnus, na prxima parte demonstraremos como publicar aquela query que criamos na classe Cliente como um mtodo de um web service...

Parte 6 Publicando a Query como um mtodo em um Webservice


Isso mesmo, podemos criar web services usando o Cach, para isso, devemos fazer nossa classe herdar da classe SOAP.WebService. Mas Barbieri... A minha classe Cliente j herda de Persistent, se eu herdar de outra classe no vou conseguir persistir os dados! Calma, diferentemente do Delphi, o Cach suporta herana mltipla e no ser nenhum problema faze-la herdar de SOAP.WebService. Inicie o Studio, abra o projeto Exemplo, expanda o pacote Exemplo e abra a classe Cliente no editor. Vamos modificar o cdigo para dizer que a classe herda de Persistent e SOAP.WebService, para isso altere a definio da classe como a seguir: Class Exemplo.Cliente Extends (%Persistent, %SOAP.WebService) [ ClassType = persistent, ProcedureBlock ] Neste momento voc no conseguir mais compilar sua classe, pois uma classe que gerar um web service deve expor web methods e pelo menos um servicename

Aps a property TelResidencial e antes da query Por nome adicione as linhas a seguir: Parameter NAMESPACE = "http://tempuri.org"; Parameter SERVICENAME = "PorNome"; Alm disso, precisamos dizer que a query PorNome agora um WebMethod, para isso adicione a informao [ WebMethod ] ao fim de sua definio: Query PorNome(oNome As %String = "") As %SQLQuery(CONTAINID = 1, ROWSPEC = "ID:%Integer,Nome,Sobrenome,CPF,Tel_Num,Tel_Pref,Tel_Ramal", SELECTMODE = "RUNTIME") [ WebMethod ] Agora compile o pacote. Reinicie seu computador antes de prosseguir! Inicie o Studio, abra o projeto Exemplo, abra a classe Cliente.

V at o menu Visualizar e selecione Pgina da Web

Lembre-se que a pgina aberta no editor deve ser a pgina que contm a classe Cliente. Nesse momento o browser se abrir e voc ver uma pgina informando dados do web service, inclusive o servio disponvel (PorNome):

Clique no link PorNome e uma nova pgina se abrir:

Informe um nome, ou parte de um nome e clique em Invoke, uma pgina com o contedo do arquivo XML gerado ser exibida no browser com os dados de todos os clientes que coincidiram com a solicitao. Bem, agora s construir uma aplicao para consumir este web service. Neste caso criaremos uma aplicao Delphi Windows Forms e adicionaremos um TextBox, um Button e um DataGrid ao form, veja a figura:

O prximo passo importar o WSDL para que possamos ter uma classe proxy em nossa aplicao, o que nos permitir chamar o mtodo no web service. Para isso, precisamos saber o endereo do WSDL, a maneira mais fcil de conseguir esse endereo novamente no Studio selecionar o menu Visualizar, clicar em Pgina Web e aps a pgina abrir no browser clicar no link Service Description, isso far abrir uma nova pgina que o WSDL, copie a url que aparece na barra de endereo do browser:

De volta ao Delphi, abra o Project Manager e clique com o boto direito sobre o nome do projeto e selecione a opo Add Web Reference...

Na janela que se abre, cole o endereo que copiamos na barra de endereos e clique na seta azul (Go)

Uma janela se abrir contendo o documento WSDL, clique em Add Reference

Volte ao Project Manager e veja que a referencia foi adicionada e que alguns arquivos foram criados, entre eles o arquivo WebReference.Exemplo.pas, este arquivo contm a classe proxy que precisamos, portanto, adicione esta unit clausula uses da unit do form (Alt+F11), pronto, agora temos tudo que precisamos para consumir o web service, vamos ento programar o click do boto, veja o cdigo: procedure TWinForm.Button1_Click(sender: System.Object; e: System.EventArgs); var Dados: PorNome; DataSetDados: DataSet; begin Dados:= PorNome.Create; DataSetDados:= Dados.PorNome(TextBox1.Text); DataGrid1.DataSource:= DataSetDados; DataGrid1.DataMember:= DataSetDados.Tables[0].ToString; end; Criamos duas variveis, uma do tipo PorNome (a nossa classe proxy. D uma olhada no arquivo gerado com a importao do WSDL para confirmar o nome da classe) que possui um mtodo chamado PorNome que recebe uma string como parmetro e retorna um Dataset. A outra varivel do tipo Dataset, poderamos ter colocado um componente

Dataset no form, mas j que o mtodo retorna um dataset, totalmente desnecessria a existncia deste componente, uma varivel do tipo (que nem precisa ser instanciada) o suficiente. O prximo passo chamar o construtor, depois disso atribumos o retorno do mtodo nossa varivel Dataset, depois, vinculamos esse dataset ao datagrid, dizemos qual membro do Dataset ele exibir e isso tudo, simples assim, veja o programa em funcionamento:

Projetos\Exemplo5.zip

Bnus
Como este um assunto empolgante vamos presentear o leitor com um bnus, vamos construir uma aplicao ASP.NET para consumir o Webservice. Crie uma aplicao ASP.NET Web Application Delphi for .NET, sugiro que voc crie uma pasta dentro de C:\Inetpub\wwwroot para salvar seu projeto. Siga os passos explicados anteriormente e adicione a web reference ao projeto;

Adicione um TextBox, um Button e um DBWebGrid pgina e posicione-os como na figura:

Vamos agora programar o manipulador do click do boto: procedure TWebForm1.Button1_Click(sender: System.Object; e: System.EventArgs); var Dados: PorNome; DatasetDados: Dataset; DataSource: DBWebDataSource; begin Dados:= PorNome.Create; DataSource:= DBWebDataSource.Create; DatasetDados:= Dados.PorNome(TextBox1.Text); DataSource.DataSource:= DatasetDados; DBWebGrid1.DBDataSource:= DataSource; DBWebGrid1.TableName:= DatasetDados.Tables[0].ToString; end;

Infelizmente a diverso acabou, nosso programa est pronto, execute-o, informe um nome ou parte de um nome no Textbox e click no boto. Veja uma imagem da pgina no IE:

Projetos\Exemplo6.zip

Parte 7 Um exemplo OO com objetos COM


Conforme prometido, nesta parte demonstraremos como criar uma aplicao OO utilizando classes Delphi para acessar os objetos do Cach via COM. No escopo deste artigo abordar tcnicas de anlise ou modelagem OO, utilizaremos o mnimo de cdigo possvel sem ferir as regras do paradigma. Utilizaremos a mesma IU (Interface do Usurio) que construmos na parte 5, e estou assumindo que voc leu as partes anteriores e j importou a type library do Cach no seu projeto. Adicione uma unit ao projeto e salve-a como uTelefone, nesta unit criaremos nossa classe TTelefone; a declarao desta classe est na listagem a seguir: type TTelefone = class private FPrefixo: string; FNumero: string; FRamal: string; procedure SetNumero(const Value: string); procedure SetPrefixo(const Value: string); procedure SetRamal(const Value: string); public property Numero: string read FNumero write SetNumero; property Prefixo: string read FPrefixo write SetPrefixo; property Ramal: string read FRamal write SetRamal; end; Adicione uma unit ao projeto e salve-a como uCliente, nesta unit criaremos nossa classe TCliente; o cdigo desta unit est na listagem a seguir: unit uCliente; interface uses uTelefone; type TCliente = class private FSobrenome: string; FCPF: string; FNome: string; FTelResidencial: TTelefone; procedure SetCPF(const Value: string); procedure SetNome(const Value: string);

procedure SetSobrenome(const Value: string); procedure SetTelResidencial(const Value: TTelefone); public property Nome: string read FNome write SetNome; property Sobrenome: string read FSobrenome write SetSobrenome; property CPF: string read FCPF write SetCPF; property TelResidencial: TTelefone read FTelResidencial write SetTelResidencial; constructor Create; destructor Destroy; override; end; implementation { TCliente } //os metodos escritores (Setxxx) foram omitidos ja que sao criados automaticamente constructor TCliente.Create; begin inherited; FTelResidencial:= TTelefone.Create; end; destructor TCliente.Destroy; begin FTelResidencial.Free; inherited; end; end. Como voc deve ter notado estas classes espelham exatamente as classes que criamos no Cach, claro que estas classes poderiam ter mais atributos e mtodos que as classes Cach, afinal, as classes Cach possuem somente os atributos que sero persistidos.

Cuidando da persistncia
Para manter a coeso de nossas classes de negocio criaremos uma classe extra que cuidar do mapeamento de nossos objetos Delphi em objetos Cach, esta classe ser responsvel por carregar o estado do objeto Cach no objeto Delphi (processo geralmente chamado de materializao) e de carregar o estado de um objeto Delphi em um objeto Cach (persistncia), alm de interfacear todo tipo de comunicao com a camada de persistncia. Esta indireo necessria para manter a coeso de nossas classes de negocio (como dito anteriormente) e tambm para permitir uma migrao da tecnologia de armazenamento (DB) sem nenhuma interferncia nas classes de negocio. Resumidamente, se a prpria

classe TCliente cuidar de sua persistncia ela estar acoplada a objetos como DataSets, DataSources e instrues SQL que podem ser especificas de determinadas tecnologias (BDE, DbExpress, ADO) ou softwares especficos (Oracle, Interbase), caso seja necessrio migrar de tecnologia ou software, ser necessrio alterar a classe de negocio TCliente, o que pode causar efeitos colaterais indesejveis. A separao das camadas de interesse outro motivo para justificar a criao desta classe de persistncia. A tcnica mais recomendada (a meu ver) a criao de uma interface com os mtodos de persistncia, assim, toda vez que precisarmos substituir alguma coisa da camada de persistncia (tecnologia ou software) estaremos bastante vontade para faz-lo. Vale a pena ressaltar que s vezes um pouco de trabalho extra inicial pode nos poupar enorme quantidade de esforo e tempo futuramente na hora da migrao, pense sempre no futuro, j que, como algum j disse muito difcil fazer previses, principalmente a respeito do futuro. Mos a massa, adicione uma unit ao projeto e salve-a como uIntPersistencia, nesta unit criaremos nossa classe interface IPersistencia, que possuir os mtodos que toda classe que se propuser a persistir um objeto do tipo TCliente devera implementar. A declarao da interface est na listagem a seguir: unit uIntPersistencia; interface uses Classes; type IPersistencia = interface function Gravar(obj: TObject; const TableName: string): boolean; function Recuperar(ID: variant; const TableName: string): TObject; function CarregarPorNome(aLista: TList; const str, TableName: string): integer; end; implementation end. Adicione uma unit ao projeto e salve-a como uPersistencia, nesta unit criaremos nossa classe TClienteDB, esta classe ir implementar IPersistencia especificamente para interagir com o Cach usando COM; o cdigo desta unit est na listagem a seguir: unit uPersistencia; interface

uses CacheObject_TLB, uCliente, Classes, uIntPersistencia; type TClienteDB = class(TInterfacedObject, IPersistencia) private Fabrica: IFactory; public destructor Destroy; override; function CarregarPorNome(aLista: TList; const str: string; const TableName: string): Integer; function Gravar(obj: TObject; const TableName: string): Boolean; function Recuperar(ID: Variant; const TableName: string): TObject; constructor Create; end; implementation uses Variants, SysUtils, Dialogs, ComObj; { TClienteDB } function TClienteDB.CarregarPorNome(aLista: TList; const str, TableName: string): Integer; var ResultSet: IResultSet; Cliente: TCliente; strMetodo: string; begin Result:= 0; strMetodo:= 'PorNome'; ResultSet:= Fabrica.ResultSet(TableName, strMetodo) as IResultSet; ResultSet.Execute(str,EmptyStr,EmptyStr,EmptyStr,EmptyStr,EmptyStr, EmptyStr,EmptyStr,EmptyStr,EmptyStr,EmptyStr,EmptyStr, EmptyStr,EmptyStr,EmptyStr,EmptyStr); while ResultSet.Next do begin Cliente:= TCliente.Create; with Cliente do begin Nome:= ResultSet.GetDataByName('Nome'); Sobrenome:= ResultSet.GetDataByName('Sobrenome'); CPF:= ResultSet.GetDataByName('CPF'); TelResidencial.Numero:= ResultSet.GetDataByName('Tel_Num'); TelResidencial.Prefixo:= ResultSet.GetDataByName('Tel_Pref');

TelResidencial.Ramal:= ResultSet.GetDataByName('Tel_Ramal'); end; aLista.Add(Cliente); end; ResultSet.Close; Result:= aLista.Count; end;

constructor TClienteDB.Create; begin Fabrica:= CreateComObject(CLASS_Factory) as IFactory; if not Fabrica.Connect('cn_iptcp:127.0.0.1[1972]:USER') then ShowMessage('No foi possvel estabelecer conexo'); end; destructor TClienteDB.Destroy; begin Fabrica.Disconnect; Fabrica:= nil; inherited; end; function TClienteDB.Gravar(obj: TObject; const TableName: string): Boolean; var Cliente: Variant; begin Result:= False; Cliente:= Fabrica.New(TableName, True); with TCliente(obj) do begin Cliente.Nome:= Nome; Cliente.Sobrenome:= Sobrenome; Cliente.CPF:= CPF; Cliente.TelResidencial.Numero:= TelResidencial.Numero; Cliente.TelResidencial.Prefixo:= TelResidencial.Prefixo; Cliente.TelResidencial.Ramal:= TelResidencial.Ramal; Cliente.Sys_Save; Cliente.Sys_Close; Cliente:= NULL; end; Result:= True; end;

function TClienteDB.Recuperar(ID: Variant; const TableName: string): TObject; var Cliente: Variant; begin Result:= TCliente.Create; try Cliente:= Fabrica.OpenId(TableName, ID, 1); except on E: Exception do ShowMessage(E.Message); end; if VarIsNull(Cliente) or VarIsClear(Cliente) then Exit; with TCliente(Result) do begin Nome:= Cliente.Nome; Sobrenome:= Cliente.Sobrenome; CPF:= Cliente.CPF; TelResidencial.Numero:= Cliente.TelResidencial.Numero; TelResidencial.Ramal:= Cliente.TelResidencial.Ramal; TelResidencial.Prefixo:= Cliente.TelResidencial.Prefixo; end; end; end. Esta classe implementa os mtodos da interface descritos a seguir: function TClienteDB.Gravar(obj: TObject; const TableName: string): Boolean; Mtodo que mapear um objeto Delphi para um objeto Cach, este mtodo recebe como primeiro parmetro um objeto do tipo TObject (embora saibamos que neste parmetro ser enviado um objeto do tipo TCliente o uso de TObject permite uma evoluo das classes que implementam esta interface para trabalharem com outros tipos de objeto, futuro... sempre futuro), este objeto dever estar devidamente instanciado e com seus valores j preenchidos, o segundo parmetro o nome da classe Cach para a qual este objeto ser mapeado (usamos TableName j que esta interface poder ser implementada por classes que acessem bancos de dados relacionais). Este mtodo retorna um boolean que informa o sucesso da operao. function TClienteDB.Recuperar(ID: Variant; const TableName: string): TObject; Mtodo que mapear um objeto do Cach em um objeto Delphi, este mtodo recebe como primeiro parmetro um integer que o ID do objeto Cach (o Cach cria um atributo ID para todas as classes, mas pode ser tambem a chave primaria de uma tabela

relacional, por isso ele do tipo variant, flexibilidade... sempre flexibilidade), o segundo parmetro o nome da classe Cach da qual este objeto ser mapeado. Este mtodo retorna um objeto do tipo TObject (embora nesta classe seja sempre um TCliente. Os motivos de usar TObject j foram explicados) devidamente instanciado e com seu estado devidamente recuperado do objeto Cach identificado pelo ID. function TClienteDB.CarregarPorNome(aLista: TList; const str, TableName: string): Integer; Este mtodo acessar a query PorNome criada na classe Cliente no Cach, este mtodo recebe como primeiro parmetro um objeto do tipo TList onde sero retornados objetos do tipo TCliente que atendam a consulta (clientes com nomes que iniciem com a string passada em str), o segundo parmetro uma string que ser repassada consulta existente na classe Cach, o terceiro parmetro o nome da classe Cach da qual os objetos sero mapeados. Este mtodo retorna um integer com o numero de objetos materializados.

Mtodos da IU
Vamos aos mtodos do form. Declare as units necessrias na clausula uses: uses uIntPersistencia, uPersistencia; Programamos o click do boto de localizar cliente pelo ID: procedure TForm1.btLocIDClick(Sender: TObject); var Cliente: TCliente; ClienteDB: IPersistencia; begin ClienteDB:= TClienteDB.Create; Cliente:= TCliente(ClienteDB.Recuperar(StrToInt(edtLocID.Text), Classe)); ShowMessage('Nome: ' + Cliente.Nome + #13 + 'Sobrenome: ' + Cliente.Sobrenome + #13 + 'CPF: ' + Cliente.CPF + #13 + 'TelRes Numero: ' + Cliente.TelResidencial.Numero + #13 + 'TelRes Ramal: ' + Cliente.TelResidencial.Ramal + #13 + 'TelRes Prefixo: ' + Cliente.TelResidencial.Prefixo); Cliente.Free; end; Observe que ClienteDB do tipo da interface (IPersistencia) e no da classe que a implementa (TClienteDB), porem, na hora de instanciarmos o objeto, usamos o construtor de TClienteDB.

Este artifcio nos permite instanciar qualquer classe que implemente IPersistencia, desacoplando dessa forma as classes cliente e servidora, veremos um exemplo em breve. A seguir programamos o click do boto que dispara a consulta por parte do nome: procedure TForm1.btLocNomeParteClick(Sender: TObject); var ClienteDB: IPersistencia; Lista: TList; i: Integer; begin ClienteDB:= TClienteDB.Create; Lista:= TList.Create; ListBox1.Clear; if (ClienteDB.CarregarPorNome(Lista, edtLocNomeParte.Text, Classe)) > 0 then for i:= 0 to Lista.Count -1 do begin with TCliente(Lista[i]) do begin ListBox1.Items.Add( 'Nome: ' + Nome + ' | Sobrenome: ' + Sobrenome + ' | CPF: ' + CPF + ' | TelNum: ' + TelResidencial.Numero + ' | TelPref: ' + TelResidencial.Prefixo + ' | TelRam: ' + TelResidencial.Ramal); end; end; Lista.Free; end; Por fim, o cdigo do click do boto que insere um novo cliente no banco: procedure TForm1.btInserirClick(Sender: TObject); var Cliente: TCliente; ClienteDB: IPersistencia; begin Cliente:= TCliente.Create; ClienteDB:= TClienteDB.Create; with Cliente do begin Nome:= edtNome.Text; Sobrenome:= edtSobrenome.Text;

CPF:= edtCPF.Text; TelResidencial.Numero:= edtTelNumero.Text; TelResidencial.Prefixo:= edtTelPrefixo.Text; TelResidencial.Ramal:= edtTelRamal.Text; end; if ClienteDB.Gravar(Cliente, Classe) then ShowMessage('Cliente gravado') else ShowMessage('No foi possvel gravar o Cliente'); Cliente.Free; end; O cdigo bastante claro e auto explicativo, mesmo assim inserimos alguns comentrios no fonte disponibilizado para download. ( Projetos\Exemplo7.zip ).

Parte 8 Utilizando uma consulta SQL com objetos COM


Embora o Cach nos permita criar mtodos e consultas em nossas classes (como visto anteriormente), a maioria dos programadores est ainda muito habituada a utilizar o SQL, a boa noticia que mesmo trabalhando orientado a objetos utilizando COM para acessar os dados no Cach podemos continuar enviando nossas instrues SQL para o Cach. Para isso devemos utilizar a interface IResultSet (j vista anteriormente), a qual nos permite enviar instrues SQL e manipular o ResultSet. Utilizaremos a aplicao criada na parte 7 para este exemplo, modifique a IU conforme a figura:

Foram adicionados um Label, um Edit (edtSQL) e um Boto (btExecSQL). Abra a unit uIntPersistencia e adicione o mtodo a seguir interface IPersistencia: function ExecutarSQL(var aLista: TList; const aSQL: string): integer; Este mtodo recebe como primeiro parmetro um objeto do tipo TList onde sero retornados objetos do tipo TCliente que atendam a consulta SQL enviada como uma string no segundo parmetro. Este mtodo retorna um integer com o numero de objetos materializados. Abra a unit uPersistencia, posicione o cursor em uma linha em branco da classe TClienteDB e tecle Ctrl + Space para abrir o Code Completion, dever aparecer o mtodo inserido na interface, selecione-o e tecle Enter, o Delphi colocar sua declarao na

classe, tecle Shift + Ctrl + C para que o Delphi gere o esqueleto do mtodo e implementeo como a seguir: function TClienteDB.ExecutarSQL(var aLista: TList; const aSQL: string): Integer; var ResultSet: IResultSet; Cliente: TCliente; begin Result:= 0; ResultSet:= Fabrica.DynamicSQL(aSQL) as IResultSet; Variant(ResultSet).Execute; while ResultSet.Next do begin Cliente:= TCliente.Create; try with Cliente do begin Nome:= ResultSet.GetDataByName('Nome'); Sobrenome:= ResultSet.GetDataByName('Sobrenome'); CPF:= ResultSet.GetDataByName('CPF'); TelResidencial.Numero:= ResultSet.GetDataByName('TelResidencial_Numero'); TelResidencial.Prefixo:= ResultSet.GetDataByName('TelResidencial_Prefixo'); TelResidencial.Ramal:= ResultSet.GetDataByName('TelResidencial_Ramal'); end; except end; aLista.Add(Cliente); end; ResultSet.Close; Result:= aLista.Count; end; Apenas chamamos o mtodo DynamicSQL de IResultSet passando como parmetro a string recebida (que deve conter uma instruo SQL vlida, como por exemplo: select * from exemplo.cliente where ID = '3' (desde que sua instancia possua ao menos 3 clientes cadastrados)). Sempre fornea o nome da classe antecedido pelo nome do pacote onde ela se encontra, ex: Exemplo.Cliente, onde Exemplo o nome do pacote e Cliente o nome da classe.

O cdigo para utilizar este metodo praticamente o mesmo que o utilizado para a consulta PorNome vista anteriormente e deve ser inserido no click do btExecSQL: procedure TForm1.btExecSQLClick(Sender: TObject); var ClienteDB: IPersistencia; Lista: TList; i: Integer; begin ClienteDB:= TClienteDB.Create; Lista:= TList.Create; ClienteDB.ExecutarSQL(Lista, edtSQL.Text); ListBox1.Clear; for i:= 0 to Lista.Count -1 do begin with TCliente(Lista[i]) do begin ListBox1.Items.Add( 'Nome: ' + Nome + ' | Sobrenome: ' + Sobrenome + ' | CPF: ' + CPF + ' | TelNum: ' + TelResidencial.Numero + ' | TelPref: ' + TelResidencial.Prefixo + ' | TelRam: ' + TelResidencial.Ramal); end; end; Lista.Free; end; Projetos\Exemplo8.zip

Parte 9 Introduzindo um segundo mecanismo de persistencia


Como dito anteriormente, a introduo de uma interface nos traria independncia e flexibilidade na hora da migrao, ou mesmo no caso de persistncias paralelas usando tecnologias/softwares diferentes, mas como diz o ditado, palavras o vento leva... vamos sair ento do campo terico e vamos ver isto na pratica, o exemplo tambm serve para demonstar como lidar com o to temido e alardeado mapeamento objeto relacional. No implementamos um mecanismo de persistncia completo, mas acredito que o exemplo demonstre de forma clara e objetiva os conceitos bsicos envolvidos no processo. Suponha que desejemos uma alternativa para persistir nossos objetos TCliente em um BD relacional, usando o bom e velho SQL e nossos componentes de acesso a dados prediletos (dbExpress, BDE, ADO, IBX), a proposta fazer isso, sem que nossa classe de negcio (TCliente) precise sofrer qualquer alterao ou manuteno, seno, no teria valido a pena o trabalho extra inicial. Animem-se amigos, coisas boas esto por vir! Uma corrente de projetistas defende a tese de que para cada classe de negocio que deve ter seus objetos persistidos, deve existir uma classe paralela que faz esta tarefa, foi este o conceito que adotamos ao criar a classe TClienteDB, porem, fomos um passo alem ao criar uma interface e agora chegou a hora de tirar proveito disso, peguem suas pedras e atirem-nas em mim se mudarmos uma nica linha da classe de negocio (TCliente). Bem, como a lgica para persistir usando objetos COM totalmente diferente da lgica para persistir usando um SGBD relacional, no h muito sentido em escrever novos mtodos para a classe existente e ficar usando condicionais para saber se chamamos o mtodo que usa COM ou o que usa SQL, ate porque, novas mudanas podero vir e a idia e que essas classes que esto prontas e debugadas no sejam mais mexidas; criaremos ento uma nova classe para realizar esta tarefa. Adicione uma nova unit e salve-a como uPersistencia2 e certifique-se de inserir o cdigo abaixo: unit uPersistencia2; interface uses CacheObject_TLB, uCliente, Classes, uIntPersistencia, ADODB; type TClienteDB2 = class(TInterfacedObject, IPersistencia) private Fabrica: IFactory; ADOQuery: TADOQuery; public destructor Destroy; override; function CarregarPorNome(aLista: TList; const str: string;

const TableName: string): Integer; function Gravar(obj: TObject; const TableName: string): Boolean; function Recuperar(ID: Variant; const TableName: string): TObject; function ExecutarSQL(var aLista: TList; const aSQL: string): Integer; constructor Create; end; implementation uses Variants, SysUtils, Dialogs, ComObj, DB; { TClienteDB2 } function TClienteDB2.CarregarPorNome(aLista: TList; const str, TableName: string): Integer; var Cliente: TCliente; begin with ADOQuery do begin Close; SQL.Clear; SQL.Add('select * from '); SQL.Add(TableName); SQL.Add(' where Nome like '); SQL.Add(QuotedStr(str + '%')); Open; end; while not ADOQuery.Eof do begin Cliente:= TCliente.Create; with Cliente, ADOQuery do begin Nome:= FieldByName('Nome').AsString; Sobrenome:= FieldByName('Sobrenome').AsString; CPF:= FieldByName('CPF').AsString; TelResidencial.Numero:= FieldByName('TelResidencial_Numero').AsString; TelResidencial.Prefixo:= FieldByName('TelResidencial_Prefixo').AsString; TelResidencial.Ramal:= FieldByName('TelResidencial_Ramal').AsString; end; aLista.Add(Cliente);

ADOQuery.Next; end; Result:= aLista.Count; end; constructor TClienteDB2.Create; begin Fabrica:= CreateComObject(CLASS_Factory) as IFactory; if not Fabrica.Connect('cn_iptcp:127.0.0.1[1972]:USER') then ShowMessage('No foi possvel estabelecer conexo'); ADOQuery:= TADOQuery.Create(nil); with ADOQuery do begin ConnectionString:= 'Provider=MSDASQL.1;Persist Security Info=False;' + 'Data Source=CACHEWEB User'; SQL.Add('select * from Exemplo.Cliente where ID=0'); end; end; destructor TClienteDB2.Destroy; begin Fabrica:= nil; ADOQuery.Close; ADOQuery.Free; inherited; end; function TClienteDB2.ExecutarSQL(var aLista: TList; const aSQL: string): Integer; var Cliente: TCliente; begin with ADOQuery do begin Close; SQL.Clear; SQL.Add(aSQL); Open; end; while not ADOQuery.Eof do begin Cliente:= TCliente.Create; with Cliente, ADOQuery do begin

Nome:= FieldByName('Nome').AsString; Sobrenome:= FieldByName('Sobrenome').AsString; CPF:= FieldByName('CPF').AsString; TelResidencial.Numero:= FieldByName('TelResidencial_Numero').AsString; TelResidencial.Prefixo:= FieldByName('TelResidencial_Prefixo').AsString; TelResidencial.Ramal:= FieldByName('TelResidencial_Ramal').AsString; end; aLista.Add(Cliente); ADOQuery.Next; end; Result:= aLista.Count; end; function TClienteDB2.Gravar(obj: TObject; const TableName: string): Boolean; begin Result:= False; with ADOQuery, TCliente(obj) do begin Close; SQL.Clear; SQL.Add('select * from Exemplo.Cliente where ID=0'); Prepared:= True; Open; Append; FieldByName('Nome').AsString:= Nome; FieldByName('Sobrenome').AsString:= Sobrenome; FieldByName('CPF').AsString:= CPF; FieldByName('TelResidencial_Numero').AsString:= TelResidencial.Numero; FieldByName('TelResidencial_Prefixo').AsString:= TelResidencial.Prefixo; FieldByName('TelResidencial_Ramal').AsString:= TelResidencial.Ramal; Post; end; Result:= True; end; function TClienteDB2.Recuperar(ID: Variant; const TableName: string): TObject; begin Result:= TCliente.Create;

//mapeamos os dados para o objeto Delphi with TCliente(Result), ADOQuery do begin Close; SQL.Clear; SQL.Add('select * from Exemplo.Cliente where ID=' + string(ID)); Open; Nome:= FieldByName('Nome').AsString; Sobrenome:= FieldByName('Sobrenome').AsString; CPF:= FieldByName('CPF').AsString; TelResidencial.Numero:= FieldByName('TelResidencial_Numero').AsString; TelResidencial.Prefixo:= FieldByName('TelResidencial_Prefixo').AsString; TelResidencial.Ramal:= FieldByName('TelResidencial_Ramal').AsString; end; end; end. Como voc j deve ter notado esta classe (TClienteDB2) implementa a mesma interface que a classe TClienteDB, portanto, possui os mesmos mtodos, porem a implementao destes mtodos diferente da outra classe, j que usa o componente ADOQuery para acessar a parte relacional do Cach e no os objetos COM. Poderamos usar outro SGBD como Interbase ou SQL Server, mas teramos que criar a base de dados neste servidores e no faria a menor diferena, o cdigo seria exatamente o mesmo j que estamos usando componentes comuns e SQL padro. Para fazer uso desta nova classe, apenas substitua as chamadas ao construtor atribudas as variveis do tipo IPersistencia, trocando TClienteDB por TClienteDB2: ClienteDB:= TClienteDB2.Create; Funciona? Claro que funciona, e viram s? Nenhuma alterao na classe alvo da persistncia (TCliente). Caso desejemos persistir em outro SGBD que no segue nenhuma destas tcnicas (COM ou SQL padro) crie uma nova classe que implemente a mesma interface e pronto. Projetos\Exemplo9.zip Para obter mais flexibilidade s mesmo se pudssemos escolher qual classe de persistncia usar de acordo com as regras de negcio. Mas... epa, regras de negocio mudam, as vezes com uma freqncia irritante. Isso tem soluo? Tem sim, e bem simples, podemos criar uma fabrica de objetos de persistncia que aplicaria as regras de negocio e instanciaria o objeto certo para cada situao... Como isso soa aos seus ouvidos? Vamos ento? Siga-me.

Parte 10 Criando uma fabrica de objetos de persistncia


Muitas vezes nos deparamos com situaes onde temos duas ou mais classes que prestam o mesmo servio, mas no sabemos de antemo qual delas iremos usar, pois a escolha depende de fatores que acontecero em runtime, ou seja, na hora da execuo do programa. So as famosas regras de negcio, situaes comuns so: aplicao de poltica de descontos, pagamentos (dinheiro, cheque, carto (Visa, Mastercard)), etc. Vamos analisar o caso da poltica de descontos. Uma loja pode ter descontos altamente variveis (vocs sabem sobre o que estou falando...) de acordo com o tipo do cliente, tipo do produto, valor total da venda, dia da semana, promoo relmpago e outros tipos inimaginveis, alem de, claro, a combinao de todas as opes acima (o cliente especial, comprou um produto em promoo e o valor total da venda atingiu o limite de desconto x), ou seja, muitas vezes necessrio aplicar algoritmos complexos e um grande Case para resolver problemas como esse, e o pior, estas regras mudam periodicamente, portanto, se queremos que nossas classes de negocio sejam estveis, devemos protege-las dessas variantes, se todo esse cdigo for colocado na classe Venda, alem de ter uma classe inchada voc ter uma classe pouco reaproveitvel , j que as regras de negocio variam muito de empresa para empresa, alem disso, essa classe estar sempre em manuteno para mudana de regras, o que poder fazer coisas essenciais que estavam funcionando parar de funcionar, por esses motivos que, em geral, migramos essas regras para classes auxiliares, essas classes tornam nosso modelo mais legvel, coeso e estvel, na hora de reaproveitar classes, as classes puras so reaproveitadas muito mais facilmente, as auxiliares as vezes podem ser reaproveitadas, as vezes precisam ser reescritas, mas o core do sistema no afetado. Muito bem, mas e ai? J que temos classes auxiliares que sero instanciadas de acordo com a regra de negocio quem o responsvel por criar objetos deste tipo? Quem define qual classe Desconto deve ser criada? Um primeiro olhar indica Venda como a classe certa, j que ela a especialista na informao, mas ai, aquele longo Case deveria estar em venda... J sabemos que no queremos isso. A soluo criar uma classe auxiliar para fazer isso, a nossa fabrica de objetos. No caso da aplicao que estamos desenvolvendo possumos duas classes de persistncia, uma que persiste atravs da tecnologia COM acessando diretamente a parte OO do Cach e outra que usa a parte relacional atravs de componentes ADO e instrues SQL. Uma alternativa seria deixar a IU cuidar disso, colocar dois checkbox para verificarmos qual est checked e ento instanciarmos o objeto correto, mas essa soluo alem de pouco elegante tambm no muito reaproveitvel, por isso vamos deixar de papo e criar nossa fabrica de objetos de persistncia. Adicione uma unit ao projeto (estamos reaproveitando o projeto que desenvolvemos na parte 9) e salve-a como uFactoryDB, o cdigo esta na lista a seguir: unit uFactoryDB; interface

uses uIntPersistencia; type TFactoryDB = class class function GetDB: IPersistencia; end; implementation uses uPersistencia, uPersistencia2; { TFactoryDB } class function TFactoryDB.GetDB: IPersistencia; begin Randomize; if Random(2) = 0 then Result:= TClienteDB.Create else Result:= TClienteDB2.Create; end; end. Como nesse exemplo no temos realmente uma regra de negocio para decidir qual classe deve ser instanciada, utilizaremos uma regra muito eficiente, o acaso... A classe possui um nico mtodo que cuidar de criar o objeto correto para o chamador, esse mtodo foi declarado como um class method para evitar que o usurio da classe tenha que declarar uma varivel e instanciar um objeto deste tipo s para criar um objeto de outro tipo (que na verdade seu objetivo), o mtodo gera um numero aleatrio entre 0 e 1, se o numero for 0 utilizaremos a classe TClienteDB (que usa COM), caso contrario, utilizaremos a classe TClienteDB2 (que usa ADO). Voc perceber a diferena entre o uso das classes quando pesquisar clientes por parte do nome, o mtodo que usa COM no Casesensitive enquanto a consulta SQL , portanto, ao tentar localizar clientes cujo nome comea com a letra a, o TClienteDB trar resultados, enquanto TClienteDB2 dar a mensagem de que a consulta no retornou resultados. Vamos agora ao form fazer as devidas alteraes: Observe como deve ficar a clausula uses: uses uCliente, uIntPersistencia, uFactoryDB; Observe que o form no precisa conhecer TClienteDB nem TClienteDB2, ele nem sabe quem estar cuidando da persistncia...

localize todas as linha onde o objeto ClienteDB instanciado (ClienteDB:= TClienteDB2.Create;) e substitua por uma chamada ao mtodo GetDB de TFactoryDB como a seguir: ClienteDB:= TFactoryDB.GetDB; Como ultima alterao, vamos inserir uma mensagem para o usurio que est realizando a pesquisa por nome, j que sabemos que uma das classes sensvel caixa, localize o cdigo do boto btLocNomeParte e envolva a chamada ao mtodo CarregarPorNome em um if como a seguir: if (ClienteDB.CarregarPorNome(Lista, edtLocNomeParte.Text, Classe)) = 0 then ShowMessage('A consulta no retornou resultados' + #13 + 'A busca pode ser sensvel caixa') else Isso tudo, agora quem decide que tipo de objeto ser instanciado a fabrica, todas as outras classes esto livres dessa carga extra, lembre-se que isso s possvel porque as classes servidoras implementam a mesma interface, ento, a IU que possui uma varivel do tipo da interface (ClienteDB: IPersistencia;) pode tranquilamente chamar os mtodos declarados na interface com a certeza de que eles existem no objeto que a varivel recebeu. Projetos\Exemplo10.zip Bem, espero que estas informaes sejam suficientes para aguar a sua curiosidade e ajud-lo a dar os primeiros passos em direo aos bancos de dados OO. Pesquise tambm sobre a capacidade do Cach de gerar pginas da web, outro assunto fascinante. Um forte abrao a todos. Ricardo Barbieri.

Ricardo Barbieri Graduado em Redes de Computadores; Ps-graduado em Anlise de Sistemas; Borland Delphi7 Studio Product Certified; Borland Delphi7 Instructor Certified; Borland Delphi8 Instructor Certified; Borland Delphi2005 for Win32 Product Certified; Borland Delphi2005 Instructor Certified. Borland Delphi2006 for Win32 Product Certified; Borland Delphi2006 Instructor Certified. Borland UML Instructor Certified.