Frederico Tomazzeti Terça-feira, 05 de setembro de 2006
Um aplicativo VFP/SQL SERVER do início ao fim - Parte 04
Um aplicativo VFP/SQL SERVER do início ao fim - Parte IValguns conceitos básicos sobre acesso ao SQL SERVER utilizando OLEDB/ADO. Para isso vou mostrar algumas funções e comentar cada uma delas.
Neste artigo as coisas parecerão um pouco confusas, pois quero passar
Estas funções serão utilizadas posteriormente na sequência dos artigos. Elas serão a base para um futuro componente "midle tier" (camada do meio) de um sistema Cliente/
Servidor.
O início
Para acessar um banco de dados diferente do banco nativo do VFP, precisamos de uma string de conexão, como vimos no final do terceiro artigo, porém geralmente precisamos configurar o nome do servidor, nome do banco, tipo de login, etc.
Para isso vamos utilizar um arquivo .INI que, nada mais é do que um arquivo texto (feito no bloco de notas) que possui alguns parâmetros e, a rotina abaixo irá ler estes parâmetros.
O arquivo UTMAG.INI possui estas linhas:
[UTMAG]
servidor=SRVUTMAG banco=UTMAG autolog=S
O código abaixo fará parte do PRG inicial do sistema. Este código cria propriedades em _SCREEN, que é visível em todo o sistema, evitanto assim criar variáveis públicas.
Desta forma, podemos mudar o nome do servidor ou do banco de dados sem precisar recompilar nosso sistema, basta alterar o arquivo .INI.
A opção "Autolog" irá determinar mais adiante se utilizaremos o login integrado com o Windows, esta opção só é possível se você utilizar Windows NT (Windows 2000) tanto no servidor (2000 server) quanto nos terminais (2000 professional) pois esta versão do sistema possui login integrado com outros aplicativos, se você possuir Windows 95, 98 ou millenium você deverá utilizar "AUTOLOG=N" e durante a inicialização do sistema pedir ao usuário que digite login e senha para acesso ao SQL SERVER. Mais adiante veremos isso.
Este é o código que irá ler os dados em UTMAG.INI.
_Screen.AddProperty("servidor",'') _Screen.AddProperty("banco",'') _Screen.AddProperty("integrado",'') _Screen.AddProperty("usuario",'')
* Buscar os dados do arquivo UTMAG.INI - parâmetros
* 1 - secção o que fica entre colchetes [ARQUIVO]
* 2 - Variável nesta secção
* 3 - opção padrão caso não encontre
* 4 - variável que receberá o valor passada por referência
* 5 - quantidade de caracteres que a variável receberá
* 6 - arquivo .ini
Declare Integer GetPrivateProfileString In Win32API As GetPrivStr ; STRING cSection ,;
STRING cKey ,;
STRING cDefault ,;
STRING @cBuffer ,;
INTEGER nBufferSize ,;
STRING cINIFile lcServidor = Space(20) lcBanco = Space(20) lcIntegrado = Space(10)
GetPrivStr('dt_sistemas', 'servidor' ,'vazio', @lcServidor , 20, 'outros\dt.ini')
GetPrivStr('dt_sistemas', 'banco' ,'vazio', @lcBanco , 20, 'outros\dt.ini')
GetPrivStr('dt_sistemas', 'autolog' ,'vazio', @lcIntegrado , 10, 'outros\dt.ini')
_Screen.servidor = Left(lcServidor, Len(Alltrim(lcServidor)) - 1) _Screen.banco = Left(lcBanco, Len(Alltrim(lcBanco)) - 1) _Screen.integrado= Left(lcIntegrado, Len(Alltrim(lcIntegrado)) - 1)
O pequeno trecho de código abaixo guarda qual o nome do usuário que está logado, esta informação será utilizada posteriormente em confirmações de login integrado.
lcNome = Sys(0)
lnPosicao = At('#',lcNome) + 2
lnTamanho =Len(lcNome) - At('#',lcNome) -1
_Screen.usuario = Substr(lcNome,lnPosicao,lnTamanho) O acesso ao banco e as regras de negócio
O código abaixo irá criar nossa classe de acesso a dados, que irá aumentar bastante durante o desenvolvimento do sistema. Vou mostrar apenas o básico para não complicar muito agora, pois quando você estiver transformando seu sistema para Cliente/Servidor você obviamente utilizará a sua estrutura já existente.
Define Class UtmagDados As Relation OlePublic lors = Null
loConn = Null cMensError = '' nNativeError = 0 Protected cStringSql,;
cServerName,;
cDataBaseName,;
cIntegrado ,;
cUserID,;
cPwd,cConnectionString,;
cCampos,cValor,cCamposSimples,cSqlWhere Procedure Init (
pServidor As String, ; pBanco As String, ; pIntegrado As String, ; pUsuario As String, ; pSenha As String ) With This
.cIntegrado = Alltrim(pIntegrado)
.DefineAtributoConexao(pServidor, pBanco, pUsuario, pSenha)
.cStringSql = ""
.cConnectionString = ""
.loConn = Null .lors = Null Endwith
Endproc Procedure Error
Lparameters nError, cMethod, nLine If This.loConn.Errors.Count>0
This.cMensError = "Numero do Error: " + ;
Transform(This.loConn.Errors.Item(0).NativeError) + Chr(13) + ;
"Descricao Error: "+This.loConn.Errors.Item(0).
Description + Chr(13) +;
"Origem do Error: "+This.loConn.Errors.Item(0).Source Else
This.cMensError = "Numero do Error: "+Transform(nError) + Chr(13) +;
"Mensagem do Error: "+Message( ) + Chr(13) +;
"Linha do Error: "+Transform(nLine) + Chr(13) +;
"Metodo: "+cMethod Endif
Endproc
Procedure Destroy With This
If Vartype(.loConn) = "O"
.Desconectar() Endif
Endwith Endproc
*************************************************************
* Procedure: DefineAtributoConexao
* Objetivo: Inicializa os atributos para a conexão com BD * Esses dados na verdade virao de um Objeto publico que * Contem Os dados para conexao com o BD
*************************************************************
Procedure DefineAtributoConexao ( ; pServidor As String, ;
pBanco As String, ; pUsuario As String, ; pSenha As String ) With This
.cServerName = Alltrim(pServidor) .cDataBaseName = Alltrim(pBanco) .cUserID = Alltrim(pUsuario) .cPwd = Alltrim(pSenha) Endwith
Endproc
O Procedimento abaixo faz a conexão com o banco de dados, observe a condição: IF .cIntegrado = 'S' - Aqui utilizamos a conexão integrada com o Windows ou não.
*************************************************************
* Procedure: Conectar
* Objetivo: Fazer a conexão com o banco de dados via ADO *************************************************************
Procedure Conectar (pConnectionString As String ) As Boolean Local llOK
With This
If Type("pConnectionString") = "C"
*-- Passou a String de conexao por parametro .cConnectionString = pConnectionString Else
If .cIntegrado = 'S'
* string pra conexão através do login do win2000 .cConnectionString = 'Provider=SQLOLEDB.1' +;
';Integrated Security=SSPI' +;
';Persist Security Info=False' +;
';Initial Catalog=' + .cDataBaseName +;
';Data Source=' + .cServerName Else
* string para conexão de segurança mista * exige usuário digitar login e senha
.cConnectionString = 'Provider=SQLOLEDB.1' +;
';Data Source=' + .cServerName +;
';trusted_connection=false;' +;
';Initial Catalog=' + .cDataBaseName +;
';User ID=' + .cUserID +;
';PassWord=' + .cPwd Endif
Endif
.loConn = Createobject("adodb.connection") If Vartype(.loConn) = "O"
.loConn.Open(.cConnectionString) If .loConn.State = 1 && Conexao Aberta llOK = .T.
Else
llOK = .F.
Endif Endif Endwith Return llOK Endproc
Esta procedure desconecta o sistema do banco de dados:
*************************************************************
* Procedure: Desconectar
* Objetivo: Fecha a conexão com o banco de dados via ADO *************************************************************
Procedure Desconectar With This
If Vartype(.lors) = "O"
If .lors.State # 0 .lors.Close() Endif
Endif
If Vartype(.loConn) = "O"
If .loConn.State # 0 .loConn.Close() Endif
Endif
.loConn = Null .lors = Null Endwith
Endproc
Os dois procedimentos seguintes criam e fecham a conexão com o banco de dados, pois não é necessário trabalhar sempre conectado. Desta forma, podemos maximizar o acesso ao banco. Imagine um terminal que é utilizado apenas a cada 30 minutos, não há necessidade de haver uma conexão permanente deste terminal com o banco.
É uma questão de economia, pois o seu cliente compra uma quantidade "X" de licenças de acesso a banco, e só pode utlizar esta quantidade, desta forma podemos multiplicar acessos com um mesmo número de licenças.
*************************************************************
* Procedure: CriaConexao
* Objetivo: Cria uma conexao publica onde todos os form's
* compartilharao acesso dessa conexao.
**************************************************************
Procedure CriaConexao
_Screen.omanipuladados = Createobject("ManipulaDados", _Screen.servidor,;
_screen.banco,;
_screen.integrado,;
_screen.usuario,;
_screen.senha)
*-- Estabeleca a conexao com o BD
_Screen.omanipuladados.conectar() Endproc
*************************************************************
* Procedure: FechaConexao
* Objetivo: Fecha uma conexao publica
**************************************************************
Procedure FechaConexao
If Vartype(_Screen.omanipuladados) = "O"
_Screen.omanipuladados.Desconectar() _Screen.omanipuladados = Null Endif
Endproc
Os 3 procedimentos seguintes serão utilizados para transações, quem já utilizou o DBC do FOX já está familiarizado com transações (Begin Transaction, End Transaction, Rollback, etc).
*************************************************************
* Procedure: IniciarTransacao * Objetivo: Abre um transact pelo ADO
*************************************************************
Procedure IniciarTransacao This.loConn.BeginTrans Endproc
*************************************************************
* Procedure: EncerrarTransacao
* Objetivo: Encerra um transact pelo ADO
*************************************************************
Procedure EncerrarTransacao This.loConn.CommitTrans Endproc
*************************************************************
* Procedure: AbortarTransacao
* Objetivo: Aborta um transact pelo ADO
*************************************************************
Procedure AbortarTransacao This.loConn.RollBackTrans Endproc
Estes dois últimos procedimentos são utilizados para executar aStored Procedureque retorna um contador sequencial da tabelaContadordo nosso banco de dados.
*************************************************************
* Procedure: ObterNovoContador
* Objetivo: Retorna contador único conforme stored procedure *************************************************************
Procedure ObterNovoContador As Integer *-- Obtem o ID_UNICO do Sistema
Return This.executarSP("obternovooid",0,"",1,"@nRetorno") Endproc
*************************************************************
* Procedure: ExecutarSP
* Objetivo: função para executar stored procedures no banco * Parametros: pNomeDaSP - nome da stored procedure * pNroParEnt - quantidade de parâmetros de entrada da SP * pParEntrada - parâmetros de entrada, iniciando com * arroba e separados por virgula
* pNroParSai - Quant. de parâmetros de saida - por * enquanto só pode ser 1 ou zero
* pParSaida - parâmetro de saída iniciando com arroba *************************************************************
Procedure executarSP As Custom
Parameters pNomeDaSP, pNroParEnt, pParEntrada, pNroParSai, pParSaida
* declaração de variáveis locais
Local loADOCmd, loADOParam && objetos
Local adInteger, adCurrency, adDate ,;
&& uso do ADO
adBoolean, adChar, adNumeric, adVarChar, AdParamInput ,;
adParamOutPut, adCmdStoredProc, AdExecuteNoRecords Local lnTamanhoString, laEntradas, laSaidas, lnElementoMatriz, ;
lcGuardarString && uso interno da função Local lnRetorno,i,llConectou
* valores utilizado pelo ADO
adInteger = 3
adCurrency = 6
adDate = 7
adBoolean = 11
adChar = 129
adNumeric = 131
adVarChar = 200
AdParamInput = 1
adParamOutPut = 2 adCmdStoredProc = 4 AdExecuteNoRecords = 128 If Vartype(This.loConn) # "O"
*-- Se nao houver conexao estabelece a conexao If !This.Conectar()
Return .F.
Else
llConectou = .T.
Endif Endif
loADOCmd = Createobject("ADODB.Command")
loADOCmd.ActiveConnection = This.cConnectionString loADOCmd.CommandText = pNomeDaSP
loADOCmd.CommandType = adCmdStoredProc * criar parametros de entrada
If pNroParEnt > 0 && monta um array com os parametros de entrada
lnTamanhoString = Len(pParEntrada) Dimension laEntradas(pNroParEnt) lcGuardarString = ''
lnElementoMatriz = 1 For i = 1 To lnTamanhoString If Substr(pParEntrada,i,1) = ','
laEntradas(lnElementoMatriz) = lcGuardarString lnElementoMatriz = lnElementoMatriz + 1 lcGuardarString = ''
Else
lcGuardarString = lcGuardarString + Substr(pParEntrada,i,1)
Endif Next
laEntradas(lnElementoMatriz) = lcGuardarString For i = 1 To pNroParEnt
loADOParam = loADOCmd.CreateParameter(laEntradas(i), adVarChar, AdParamInput)
loADOCmd.Parameters.Append(loADOParam) Next
Endif
* criar parametros de saída (retorno da stored procedure) If pNroParSai > 0
lnTamanhoString = Len(pParSaida) && monta um array com os parametros de saida
Dimension laSaidas( pNroParSai) lcGuardarString = ''
lnElementoMatriz = 1 For i = 1 To lnTamanhoString If Substr(pParSaida,i,1) = ','
laSaidas(lnElementoMatriz) = lcGuardarString lnElementoMatriz = lnElementoMatriz + 1 lcGuardarString = ''
Else
lcGuardarString = lcGuardarString + Substr(pParSaida,i,1)
Endif Next
laSaidas(lnElementoMatriz) = lcGuardarString For i = 1 To pNroParSai
loADOParam = loADOCmd.CreateParameter(pParSaida, adInteger, adParamOutPut)
loADOCmd.Parameters.Append(loADOParam) Next
Endif
loADOCmd.Execute(,,AdExecuteNoRecords)
lnRetorno = loADOCmd.Parameters(pParSaida).Value loADOCmd = Null
If llConectou This.Desconectar() Endif
If pNroParSai = 1 Return lnRetorno Else
Return -1 Endif Endproc EndDefine
As coisas parecem um pouco fora do lugar neste momento mas são estes procedimentos (e outros de menor relevância que veremos com a sequência dos trabalhos) que fazem todo o trabalho de acesso e manipulação de dados.
É fundamental estudar ADO, para isso recomendo os artigos da MSDN sobre o assunto e um livro que me ensinou muito foi "Dominando SQL Server 2000 - A Biblia".
Outro livro que auxiliará bastante, principalmente na quinta parte deste artigo é o "Desenvolvendo soluções XML com Visual FoxPro" do nosso colega Fábio Vazquez.
Por fim, agradeço ao Breno Viana, nosso colega de UT e meu colega de trabalho, pois tem muita coisa dele nas funções que apresentei aqui.