Página 1 de 3

Retornar a Conexão do banco de Dados

Enviado: 27 Jun 2016 15:36
por Antonio
Boa tarde pessoal.
Estou tentando implementar um classe de conexão com um BD.

Código: Selecionar todos

#include "adodb.ch"
#include "hbclass.ch"

CLASS TConexao
	DATA dbRs       INIT "ADODB.Connection" 
	DATA dbDriver   INIT "{PostgreSQL UNICODE}"
	DATA dbDataBase INIT "Plainning"
	DATA dbServer   INIT "127.0.0.1" 
	DATA dbPort     INIT 5432
	DATA dbUser     INIT "postgres"
	DATA dbPassword INIT "ccccccc"
	
	METHOD conectar() CONSTRUCTOR
	METHOD desconectar()
ENDCLASS
/*
METHOD novo() CLASS TConexao
	::dbUser     := NIL
	::dbPassword := NIL
RETURN Self
*/
METHOD conectar( cUser, cPass ) CLASS TConexao
       public dbConn
       Try
	 dbConn:= win_oleCreateObject( "ADODB.Connection"  )
	 dbConn:Open("Driver={PostgreSQL UNICODE};Server=127.0.0.1;Port=5432;Database=plainning;Uid=postgres;Pwd=ccccccc;" )   
		 MsgBox ( "CONETADO", "Plainning",,)
		
	   Catch err
	      MsgBox( "Deu Ruim")
		  dbConn:Close()
	   end 
RETURN Self

METHOD desconectar() CLASS TConexao
	dbRecSet:Close()
RETURN dbConn


Estou fazendo a chamada assim:

Código: Selecionar todos

#include <hmg.ch>

declare window Main

Function main_button_teste_action
	
	oConn:= TConexao():conectar("postgres", "mypassword")
	dbCommand :="select * FROM tab_ctspagar;"
	dbRecSet := oConn:EXECUTE ( dbCommand )

	
	do while .not. dbRecSet:eof()
     msgbox( str( dbRecSet:fields["cpo_numero"]:VALUE ) +" "+  sTr( dbRecSet:fields["cpo_data"]:VALUE ) +" "+ str( dbRecSet:fields["cpo_valor"]:VALUE ), "splain" )
     dbRecSet:MoveNext()
	enddo
	
Return Nil
A conexão com o BD é feita normalmente mas ocorre o problema que esta na imagem:
O que eu queria é instanciar a classe fazendo com que o oBjeto da conexão ficasse disponivel/visivel a todo o sistema para assim, executar os métodos.
Agradeço a ajuda.
Obrigado

Retornar a Conexão do banco de Dados

Enviado: 27 Jun 2016 15:46
por JoséQuintas
Uso assim em multithread:

Código: Selecionar todos

STATIC AppcnMySqlLocal := NIL

FUNCTION AppcnMySqlLocal()

   IF AppcnMySqlLocal == NIL
       AppcnMySqlLocal := MySqlConnection( "xxx", , "xxx" )
   ENDIF

   RETURN AppcnMySqlLocal
E nas rotinas:

Código: Selecionar todos

LOCAL cnMySql := ADOClass():New( AppcnMySqlLocal() )

cnMySql:cSql := "SELECT * FROM CLIENTES"
cnMySql:Execute()
DO WHILE .NOT. cnMySql:Eof()
   ? cnMySql:Value( "CODIGO" )
   cnMySql:MoveNext()
ENDDO
cnMySql:Rs:Close()
Minha classe já tem recordset e tudo mais junto.
Por isso acabo criando instâncias da classe, e não da conexão.

Retornar a Conexão do banco de Dados

Enviado: 27 Jun 2016 18:29
por JoséQuintas
Só pra curiosidade, sem tratamento de erros algo mais ou menos assim:

Código: Selecionar todos

CREATE CLASS ADOClass

   VAR Connection
   VAR Rs
   VAR cSql

   METHOD New( oCn )         INLINE ::Connection := oCn
   METHOD Value( cCampo )    INLINE ::Rs:Fields( cCampo ):Value
   METHOD Eof()              INLINE ::Rs:Eof()
   METHOD MoveFirst()        INLINE ::Rs:MoveFirst()
   METHOD MoveLast()         INLINE ::Rs:MoveLast()
   METHOD RecordCount()      INLINE ::Rs:RecordCount()
   METHOD Execute( cSql )    INLINE ::Rs := ::Connection:Execute( iif( cSql == NIL, ::cSql, cSql ) )
   METHOD ExecuteCmd( cSql ) INLINE ::Connection:Execute( iif( cSql == NIL, ::cSql, cSql ) )

   ENDCLASS
Por falar nisso... como não fecho a conexão na classe, mais fácil criar um close do recordset nela, pra evitar esquecimento e fechar a conexão por engano.

Código: Selecionar todos

METHOD Close() INLINE ::Rs:Close()

Retornar a Conexão do banco de Dados

Enviado: 27 Jun 2016 22:27
por Vlademiro
Você não criou o método EXECUTE. É isso que a mensagem está dizendo.

Faz tempo que não uso ADODB, mas eu tentaria mais ou menos assim :

1 ) Não criaria uma variável public dbConn, eu faria assim :

Código: Selecionar todos

     DATA dbConn // Nas definições da classe
2 ) Apagaria a linha public dbConn e alteraria a referencia dbConn por ::dbConn no método conectar, já que ele agora é interno a classe

3 ) Criaria o meu método execute

Código: Selecionar todos


METHOD Execute( cSuaQuery ) CLASS TConexao

    
RETURN  ::dbConn.Execute( cSuaQuery ) 

Agora sim, o Execute é visível.


Você pode evoluir a sua classe mais adiante, criando outros métodos. A vantagem é que ela pode abranger a vários banco de dados. Evite usar variáveis públicas dentro de objetos.

Retornar a Conexão do banco de Dados

Enviado: 28 Jun 2016 08:25
por asimoes
Vlademiro

É isso
::dbConn.Execute( cSuaQuery )

Ou isso: ?

::dbConn:Execute( cSuaQuery )

Retornar a Conexão do banco de Dados

Enviado: 28 Jun 2016 08:58
por Vlademiro
Com dois pontos asimoes

::dbConn:Execute( cSuaQuery )

Valeu.

Aproveito para acrescentar que a solução do Quintas é a mais completa, e sugiro ao Antonio não ir logo usando ela pois seria melhor para ele aprender esses pequenos detalhes e ir ele mesmo desenvolvendo a sua classe. Ele pode ir desenvolvendo, postando suas duvidas aqui e ir olhando as classes já desenvolvidas, como a do Quintas. Seria mais proveitoso assim...

Retornar a Conexão do banco de Dados

Enviado: 28 Jun 2016 10:07
por Antonio
Muito obrigado a todos!
Jose Quintas é bem sofisticada a sua metodologia.

Eu estou usando um arquivo de cabeçalho que encontrei em um exemplo da minigui. adodb.ch
Parece que voces não fazem o uso deste.

Não me encontrei ainda.

Grato

Retornar a Conexão do banco de Dados

Enviado: 28 Jun 2016 10:25
por JoséQuintas
Só voltando um pouco atrás:

A conexão é... uma conexão. Uma classe de conexão parece não trazer nenhum recurso adicional pra conexão.
Basta uma variável.

O que meu aplicativo faz: cria no início, fecha no final (exceto as de uso temporário)

Minha função da conexão está assim, pode até criar uma pra cada tipo de banco de dados:

Código: Selecionar todos

FUNCTION MySqlConnection( cServer, nPort, cDatabase, cUser, cPassword, nVersion )

   LOCAL cnConnection

   hb_Default( @cServer, "nomeservidor" )
   hb_Default( @cDatabase, "nomebancodedados" )
   hb_Default( @nPort, 3306 )
   hb_Default( @cUser, "usuario" )
   hb_Default( @cPassword, "senha" )
   hb_Default( @nVersion, 3 )

   cnConnection:= win_OleCreateObject( "ADODB.Connection" )
   cnConnection:ConnectionString := "Driver={MySQL ODBC " + iif( nVersion == 3, "3.51", "5.3 ANSI" ) + " Driver};Server=" + cServer + ";" + "Port=" + Ltrim( Str( nPort ) ) + ;
      ";Stmt=;Database=" + cDatabase + ";User ID=" + cUser + ";Password=" + cPassword + ";Collation=latin1;" + ;
      "AUTO_RECONNECT=1;COMPRESSED_PROTO=0;PAD_SPACE=1" // usando compactação impede certas checagens // Option=131072;
   cnConnection:CursorLocation    := AD_USE_CLIENT
   cnConnection:CommandTimeOut    := 200 // seconds
   cnConnection:ConnectionTimeOut := 200 // seconds
   // cnConnection:ConnectionString := "Driver={MySQL ODBC 5.3 ANSI Driver};Server=" + cServer + ";" + "Port=" + Ltrim( Str( nPort ) ) + ;

   RETURN cnConnection

Retornar a Conexão do banco de Dados

Enviado: 28 Jun 2016 10:31
por JoséQuintas
E a classe contém o recordset embutido, vou colocando coisas conforme vou achando que pode facilitar.
Tratamento de campo nulo, geração pra dbf, no execute testar se conexão está aberta, etc.

Código: Selecionar todos

CREATE CLASS ADOClass

   VAR    Cn
   VAR    Rs
   VAR    cSql
   VAR    oStructure

   METHOD New( oConnection )           INLINE ::CN := oConnection, SELF
   METHOD Open( lError )
   METHOD Close()
   METHOD Execute( cSql, lError )    // Atualiza Rs com retorno
   METHOD ExecuteCmd( cSql, lError ) // Despreza retorno
   METHOD ExecuteDbf( cSql )
   METHOD StringSql( xField, nLen )
   METHOD NumberSql( xField )
   METHOD DateSql( xField )
   METHOD Value( xField )
   METHOD ValueSQL( xField )           INLINE ::Value( xField )
   METHOD Replace( cFrom, cTo )
   METHOD Eof()                        INLINE iif( ::Rs:RecordCount() == 0, .T., ::Rs:Eof() )
   METHOD MoveFirst()                  INLINE ::Rs:MoveFirst()
   METHOD MoveNext()                   INLINE ::Rs:MoveNext()
   METHOD SQLToDBF( lStruAuto ) // usar ExecuteDbf()
   METHOD Count( cSql )
   METHOD TableList()
   METHOD TableExists( cTable )
   METHOD FieldList( cTable )
   METHOD FieldExists( cField, cTable )
   METHOD AddField( cField, cTable, cSql )
   METHOD DeleteField( cField, cTable, cDbf )
   METHOD IndexList( cTable )
   METHOD RecordCount()                INLINE ::Rs:RecordCount()

   END CLASS
Nota:

o INLINE é só uma economia de fonte. Quando a função é muito pequena, usando INLINE coloca direto na declaração, sem função separada para o método.

Exemplo:

Código: Selecionar todos

FUNCTION Teste() ; RETURN "ok"

METHOD Teste() INLINE "ok"
ou comparando:

Código: Selecionar todos

CREATE CLASS ...
   METHOD Teste()
   END CLASS

METHOD Teste() CLASS ...
   RETURN "ok"
//---------------------------
CREATE CLASS ...
   METHOD Teste() INLINE "ok"
   END CLASS

Retornar a Conexão do banco de Dados

Enviado: 28 Jun 2016 10:48
por JoséQuintas
Acréscimo:

Algumas aí foram temporárias e não eliminei.
Porque eu errava o nome, então coloquei dois nomes, como Value() e ValueSQL()
Ou porque decidi alterar o nome no meio do caminho, e deixei os dois nomes até terminar de alterar todos os fontes
Ou porque dependendo do ODBC, o tipo não é convertido corretamente, então StringSql() garante que vai ser convertido pra string.
Como tenho ODBC 3.51 e 5.3 nos clientes, e cada um se comporta diferente, algumas coisas vão ficar aí até terminar de trocar todos.

SqlToDBf e ExecuteToDbf também, são a mesma coisa, mas talvez altere o nome novamente.... rs

Retornar a Conexão do banco de Dados

Enviado: 28 Jun 2016 11:14
por Vlademiro
Quintas, nas minhas classes eu até comecei fazendo assim (conexão e recordset na mesma classe), mas me deparei com um problema : nos CRUDs que eu criava eu primeiro abria um Grid para depois chamar o Formulário. Alguns métodos do Grid (Recordset) não tem relação com o Formulário. Por exemplo: nos forms eu tenho o método Insert, Update e Delete. No Grid eu tenho o método NextPage, PreviousPage, etc.

Ficou assim :

Código: Selecionar todos

TVladSQL  +---->  TVladSQLBrowser
          |
          +----> TVladSQLFrm     

Documentação da TVladSQL : http://sistema.lia.ufc.br/robodoc/TVlad ... ml#robo116
Documentação da TVladSQLBrowser : http://sistema.lia.ufc.br/robodoc/TVlad ... tml#robo77
Documentação da TVladSQLFrm : http://sistema.lia.ufc.br/robodoc/TVlad ... tml#robo61

Retornar a Conexão do banco de Dados

Enviado: 28 Jun 2016 11:18
por Vlademiro

Retornar a Conexão do banco de Dados

Enviado: 28 Jun 2016 12:15
por JoséQuintas
Você trabalha diferente do jeito que eu trabalho.
Considero o recordset igual a um arquivo temporário.
Não me preocupo em manter posição, ou seja lá o que for.
No caso do browse uso um recordset só pra ele, e até converto pra DBF.
Ao alterar num browse, por exemplo, a partir do ID atualizo o MySQL.
Achei mais prático assim, do que enfrentar particularidades.

É isso que indica lá na minha conexão o CursorLocation AD_USE_CLIENT
É praticamente como se o aplicativo estivesse desconectado da base, mesmo mantendo conexão aberta.

Retornar a Conexão do banco de Dados

Enviado: 28 Jun 2016 13:03
por Vlademiro
Entendi, os arquivos temporários que vc trabalha são DBFs, e na hora de pegar os dados do servidor fica mais pratico fazer com uma classe com tudo junto (Conexão e Recordset). Quando os dados chegam você os trata localmente usando os DBFs. Quando você vai gravar os dados você os converte de volta para o banco de dados.

Retornar a Conexão do banco de Dados

Enviado: 28 Jun 2016 13:06
por Vlademiro
Vou postar um teste de sistema que fiz usando HMG. Lá tem as classes que fiz até agora, os formulários e o banco em postgreSQL.

Vou postar na seção Minigui do fórum. Lá da pra ver como eu fiz o CRUD gravando no postgreSQL.