FiveTech Support Forums

FiveWin / Harbour / xBase community
Board index FiveWin para Harbour/xHarbour HOWTO. ADO, SQL Server, Procedimientos Almacenados
Posts: 731
Joined: Fri Oct 07, 2005 07:42 AM
HOWTO. ADO, SQL Server, Procedimientos Almacenados
Posted: Thu Nov 27, 2008 04:10 PM
ADO, SQL Server, Procedimientos Almacenados y otras historias....

En un traspaso de datos desde DBF a SQL Server, quizás nos veamos en la necesidad
de llamar a un procedimiento almacenado, donde necesitamos enviar parámetros y
además necesitamos obtener datos que se modifican dentro del procedimiento almacenado.

Para ello, me he creado un par de clases, partiendo de las de Jose Luis Capel,
que nos permitirá llamar a un procedimiento almacenado, pasando valores y
obteniendo valores.

Por ejemplo algo como esto en el procedimiento almacenado ;
ALTER PROCEDURE [dbo].[SP_CREAR_EJEMPO]
  @pCODIGO            VARCHAR(3),
  @pPRECIO            INT,
  @pDESCRIPCION       VARCHAR(40),
  @pID                INT output



El ejemplo para llamar a un procedimiento es el siguiente;

Creamos el procedimiento almacenado llamado SP_CREAR_EJEMPO a traves de la clase TAdoStoreProc.
Nota: oConnection es la clase TAdConnect de Jose Luis Capel.

o
Cmd := TAdoStoreProc():New( "SP_CREAR_EJEMPO", oConnection )
   oAdoCmd := oCmd:GetADOCommand() // Obtemos el objeto ADO Command.


A partir de este momento, solamente vamos a alimentar a nuestras variables
del procedimiento almacenado;

o
ADOCmd:Parameters:Item('@CODIGO'):Value  := '001'
  oADOCmd:Parameters:Item('@pPRECIO'):Value :=  100.56
  oADOCmd:Parameters:Item('@pDESCRIPCION'):Value := 'Producto varios'

  oAdoCmd:Execute()

Ahora imaginemos que en el procedimiento Almacenado tenemos una variable
llamada por ejemlo @pID, que no es más que un Identity del último INSERT, que
nos servirá para hacer SELECT hacia otra tablas relacionadas, que nos a creado
el SP( Store Procedure ), por poner un ejemplo;

n
ID := oADOCmd:Parameters:Item('@pID'):Value // Obtenemos el ID insertado

cSql := [ SELECT * FROM TABLA WHERE ID_MASTER = ] + str( nID )

Este es un simple ejemplo de lo que podría suceder.*( Basado en hechos reales :-) )

Bueno, ahora pondré el código fuente

#define adCmdStoredProc 4
---------------------------------------------------------------------
//---------------------------------------------------------------------
CLASS tAdoCommand
    DATA oCmd          //Objeto ADO Command
    DATA oParameters
    DATA oError

    METHOD New( oConnection ) CONSTRUCTOR
    METHOD Type( nType ) INLINE ::oCmd:CommandType := nType
    METHOD Text( cText ) INLINE ::oCmd:CommandText := cText
    METHOD GetADOCommand()  INLINE ::oCmd
END CLASS

METHOD New( oConnection ) CLASS tAdoCommand

       LOCAL oHErr

       ::oError := TAdoError():New()

       TRY
          ::oCmd := CreateObject("ADODB.Command")
       CATCH oHErr
          ::oError:Connection( oConnection )
          Throw( oHErr )
       END

       ::oCmd:ActiveConnection := oConnection:oConnection

RETURN Self



//---------------------------------------------------------------------
CLASS TAdoStoreProc FROM TAdoCommand
      METHOD New( cText, oConnection ) CONSTRUCTOR
      METHOD Execute()
END CLASS

METHOD New( cText, oConnection ) CLASS tAdoStoreProc
  Local oHErr

  try
    Super:New( oConnection )
    ::Type( adCmdStoredProc )
    ::Text( cText )
    ::oCmd:Parameters:Refresh()
  catch oHErr
    ::oError:Connection( oConnection )
    Throw( oHErr )
   end

RETURN Self

METHOD Execute( ) CLASS tAdoStoreProc
       LOCAL oError, cError, oErr
       LOCAL lResult

       lResult := .T.

       TRY
         ::oCmd:Execute()
       CATCH oErr
          ::oError:Connection( ::oConnection )
          lResult := .F.
          Throw( oErr )
       END

RETURN lResult
 
//---------------------------------------------------------------------
CLASS TAdoError
      DATA aErrors
      DATA oErrorDB
      METHOD New()
      METHOD Clean() INLINE ::aErrors := {}
      METHOD Connection( oConnection )
      METHOD ShowErrors()
END CLASS

METHOD New( lCreateError ) CLASS TAdoError

   ::aErrors := {}
   /* TODO: Pendiente de I+D
   if lCreateError
      ::oErrorDB := CreateObject( "ADODB.Error")
   endif
   */
RETURN Self

METHOD Connection( oConnection ) CLASS TAdoError

    Local oError, cError := ""

    ::Clean()

    For Each oError In oConnection:Errors
        cError := "Error #" + cValtoChar( oError:Number ) + CRLF +;
                  "   "  + oError:Description + CRLF +;
                  "   (Source: " + oError:Source + ")" + CRLF +;
                  "   (SQL State: " + oError:SQLState + ")" + CRLF +;
                  "   (NativeError: " + cValToChar( oError:NativeError ) + ")"
        AADD( ::aErrors, cError )
    Next

RETURN NIL

METHOD ShowErrors( ) CLASS TAdoError
    Local cError := ""

    For Each cError In ::aErrors
        MsgStop( cError )
    next

RETURN NIL


Bueno, yo controlo los 'posibles' errores desde el nivel más alto, con try /catch.

Las clases si os fijais hacen Throw( oErr ), por si fuera un error de Harbour, despues discrimino si el error es de ADO o de Harbour, no vaya a ser que sume un numerico + "ALAAA" y le estemos echando la culpa a ADO
cuando realmente no tiene la culpa. :-)

Por ejemplo;

try 
  oConnection:BeginTrans()
  Cmd := TAdoStoreProc():New( "SP_CREAR_EJEMPO", oConnection )
  oAdoCmd := oCmd:GetADOCommand() // Obtemos el objeto ADO Command.
  oADOCmd:Parameters:Item('@pHASTA'):Value := DTOC(  oDbMPrecios:Hasta )

 oCmd:Execute()
catch oError
  oConnection:RollBackTrans()
  ? "ERROR. Se ha realizado ROLLBACK." + CRLF  )
  lResult := .F.
  if !ShowError( oError, oConnection )
     ? "Falla el procedimiento / Sentencia SQL"
  endif
end
#define NTRIM(n)    ( LTrim( Str( n ) ) )

Function ShowError( oError, oConnection )
   Local cErrorLog := "", n, aStack := {}
   Local lError := .F.

   if Empty( oConnection:oError:aErrors ) // No son errores de ADO
     lError := .T.
     cErrorLog += "Operation: "+ oError:Operation + CRLF
     cErrorLog += "Error description: "+ oError:Description + CRLF
     if ValType( oError:Args ) == "A"
        cErrorLog += "   Args:" + CRLF
         for n := 1 to Len( oError:Args )
             cErrorLog += "     [" + Str( n, 4 ) + "] = " + ValType( oError:Args[ n ] ) + ;
                          "   " + cValToChar( oError:Args[ n ] ) + CRLF
         next
     endif
     cErrorLog += CRLF + "Stack Calls" + CRLF
     n := 1
     while ( n < 5 ) // Las ultimas 5
          if ! Empty(ProcName( n ) )
             AAdd( aStack, "   Called from: " + ProcFile( n ) + " => " + Trim( ProcName( n ) ) + ;
                           "(" + NTRIM( ProcLine( n ) ) + ")" )
             cErrorLog += ATail( aStack ) + CRLF
          endif
          n++
     end
     MsgStop( cErrorLog , "Error Harbour" )
   endif

   oConnection:oError:ShowErrors() // Enseña algo, si tienes que enseñar

return lError


También existe otra forma, CreateParameter(), pero no he profundizado en ello, os lo dejo como ejercicio.

Bueno, dar las gracias a J.Capel por compartir su clases de ADO, esto es un complemento a ese trabajo.


Saludos
Rafa Carmona
Saludos

Rafa Carmona ( rafa.thefullARROBAgmail.com___quitalineas__)

Continue the discussion