Como utilizar parâmetros em dbExpress (DBX) Framework

Em reposta a dúvida do Rafael e Carlos Gonzaga sobre como criar consultadas parametrizadas com dbExpress Framework resolvi escrever sobre o tema.

Para começar, existe uma pequena diferença ao utilizar parâmetros em SQL com DBX Framework e VCL. Enquanto a VCL utiliza “:PARAMETRO” para definir parâmetros em uma query, em DBX Framework parâmetro é representado pelo sinal de “?” e não inclui nome.

Como estamos acostumados com a VCL e certamente é muito mais legível codificar utilizando o nome dos parâmetros, você espera trabalhar da mesma forma com DBX Framework e isso é possível criando uma pequena extensão ao DBX Framework.

Para entender melhor, caso você utilize puramente o framework, seu código seria assim:

program DBX4Example;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  DBXDynalink,
  DBXCommon,
  DBXInterbase;

var

  aDBXConn: TDBXConnection;
  aDBXTrans : TDBXTransaction;
  aCmnd: TDBXCommand;
  aReader: TDBXReader;

begin

    aDBXConn := TDBXConnectionFactory.GetConnectionFactory.GetConnection('employee ib','sysdba','masterkey');

    if aDBXConn <> nil then
    begin

      aCmnd := aDBXConn.CreateCommand;

      // Start transaction
      aDBXTrans:= aDBXConn.BeginTransaction(TDBXIsolations.ReadCommitted);

      // Prepare and execute the SQL Statement
      aCmnd.Text := 'SELECT * FROM Country  Where Country = ?';
      aCmnd.Prepare;
      aCmnd.Parameters.Parameter[0].Value.SetAnsiString('USA');
      aReader := aCmnd.ExecuteQuery;

      while aReader.Next do
      begin
        Writeln( aReader.Value['Country'].GetAnsiString );
      end;

      // Commit transaction
      aDBXConn.CommitFreeAndNil(aDBXTrans);

      Readln;
      aReader.Free;
      aCmnd.Free;
      aDbxConn.Free;
    end;
end.

Veja que a query utiliza interrogação para definir o parâmetro e após o Prepare a longa linha para definir o valor do parâmetro com posição zero.

Esta primeira parte responderia a pergunta do Rafael e do Carlos, mas podemos tornar esse código mais amigável utilizando Class Helpers, recurso este que foi implementado no Delphi 2006 e nos ajuda a extender classes sem utilizar herança.

Assim sendo vou estender a classe TDBXParameterList, o qual irá permitir que eu nomeie os parâmetros e define seus respectivos valores por nome.

O código abaixo estende a classe utilizando a sintaxe class helper, veja bem isso não é herança. Esta extensão define os seguintes métodos:
SetParamatersName: este método irá receber uma lista de nomes, o qual será usado para nomear os parâmetros, você deve passar a quantidade de nomes igual a quantidade de parâmetros;
ByName: método que nos permite acessar os parâmetros através do nome, o DBX Framework traz o método GetOrdinal que permite acessar os parâmetros por nome, o ByName irá simplificar este acesso;

unit DBXParameterListHelper;

interface

uses
  DBXCommon, SysUtils;

type
  TDBXParameterListHelper = class helper for TDBXParameterList

  public
    procedure SetParamatersName(aNames: Array of String);
    function ByName( Name : String ) : TDBXWritableValue;
  end;

implementation

{ TDBXParameterListHelper }

function TDBXParameterListHelper.ByName(Name: String): TDBXWritableValue;
begin
  Result := Self.Parameter[Self.GetOrdinal(Name)].Value;
end;

procedure TDBXParameterListHelper.SetParamatersName(aNames: array of String);
var
  i: Integer;
begin

  if Length(aNames) <> Self.Count then
     raise Exception.Create('aNames should have the same number of parameters');

  for i := 0 to Self.Count - 1 do
  begin
    Self[i].Name := aNames[i];
  end;
end;

end.

Com está extensão esta parte do código:

      // Prepare and execute the SQL Statement
      aCmnd.Text := 'SELECT * FROM Country  Where Country = ?';
      aCmnd.Prepare;
      aCmnd.Parameters.Parameter[0].Value.SetAnsiString('USA');

Ficaria assim:

      // Prepare and execute the SQL Statement
      aCmnd.Text := 'SELECT * FROM Country  Where Country = ?';
      aCmnd.Prepare;
      aCmnd.Parameters.SetParamatersName(['COUNTRYNAME']);
      aCmnd.Parameters.ByName('COUNTRYNAME').SetAnsiString('USA');

Caso a query tenha vários parâmetros, ficaria assim:

      // Prepare and execute the SQL Statement
      aCmnd.Text := 'SELECT * FROM Country  Where Country = ? and Currency = ?';
      aCmnd.Prepare;
      aCmnd.Parameters.SetParamatersName(['COUNTRYNAME', 'CURRENCY']);
      aCmnd.Parameters.ByName('COUNTRYNAME').SetAnsiString('USA');
      aCmnd.Parameters.ByName('CURRENCY').SetAnsiString('Dolar');

Ao final vimos como utilizar consultas parametrizadas com DBX Framework e como utilizar class helper para estender classes.

Download do código fonte aqui.

18 respostas
  1. Leandro
    Leandro says:

    Um bom exercício, mas acho que a utilidade pode não ser tão boa, em comparação com a VCL.
    Eu notei, especialmente, dois problemas:
    – Não posso colocar os parâmetros na query, lá ainda temos o “?”
    – Não posso usar um mesmo parâmetro em dois pontos na query (dois parâmetros com o mesmo nome)

    Responder
    • Andreano Lanusse
      Andreano Lanusse says:

      Oi Leandro,

      Na forma que implementei não é possível ter parâmetros com o mesmo nome, mas como os parâmetros são objetos, temos o caminho para fazer isso. Sobre não poder colocar o parâmetro na query, não entendi, visto que “?” é o parâmetro.

      A idéia aqui não é tornar o framework a VCL e sim tornar o desenvolvimento mais amigável com o framework. Além disso a performance do framework é muito maior que a da VCL para operações onde não temos data aware.

      Responder
  2. Carlos Gonzaga
    Carlos Gonzaga says:

    Oi Andreano
    Bom exemplo
    Não sei se estou implementando errado, mas seguindo o exemplo padrão (não implementando as extensões), dá um erro “Invalid Ordinal : 0”

    Nota: Uso sql server express 2005 sp3 com windows 7 utimate/windows xp sp3

    Verifiquei tbm q depois do “Prepare” a propriedade “IsPrepared” fica false

    Responder
  3. Rafael
    Rafael says:

    Achei o post uma maravilha, mas gostaria de saber como faço para passar outros tipos de valores para os parâmetros como por exemplo do tipo TDBXDate.

    Valeu!

    Responder
  4. Carlos Gonzaga
    Carlos Gonzaga says:

    Mas seguindo o exemplo como lhe disse, o “0” é a ordem do parametro:

    con := CDataBase(‘SQL2005CONN’);
    cmd :=con.CreateCommand ;
    cmd.Text :=’select *from cadpro00 where pro00_codsvc = ?’;
    cmd.Parameters.Parameter[0].Value.SetInt16(107);
    // if cmd.IsPrepared then
    reader :=cmd.ExecuteQuery ;

    Ou seja o “0” q estou passando é justamente a posição do primeiro “?” não é isso?
    E outra pq a propriedade “IsPrepared” do TDBXCommand não fica “True” !!!

    Responder
  5. Carlos Gonzaga
    Carlos Gonzaga says:

    Sim é justamente ai q da o erro: “Invalid Ordinal : 0″ quando executo a query.
    O q pode estar acontecendo? será q não funciona com o driver do sql server?

    Responder
  6. Rafael
    Rafael says:

    O erro “Invalid Ordinal” que os comentários acima relataram é porque o código apresentado pelo Andreano não cria o parâmetro antes de atribuí-lo o valor ‘USA’.
    Segue código corrigido:

    program DBX4Example;

    {$APPTYPE CONSOLE}

    uses
    SysUtils,
    DBXDynalink,
    DBXCommon,
    DBXInterbase;

    var

    aDBXConn : TDBXConnection;
    aDBXTrans : TDBXTransaction;
    aCmnd : TDBXCommand;
    aReader : TDBXReader;
    aParam : TDBXParameter;
    begin
    aDBXConn := TDBXConnectionFactory.GetConnectionFactory.GetConnection('employee ib','sysdba','masterkey');

    if aDBXConn nil then begin
    aCmnd := aDBXConn.CreateCommand;

    // Start transaction
    aDBXTrans:= aDBXConn.BeginTransaction(TDBXIsolations.ReadCommitted);

    // Prepare and execute the SQL Statement
    aCmnd.Text := 'SELECT * FROM Country Where Country = ?';

    aParam := aCmnd.CreateParameter;
    aParam.DataType := TDBXDataTypes.WideStringType;
    aParam.Name := 'COUNTRY';

    aCmnd.Prepare;
    aCmnd.Parameters.Parameter[0].Value.SetAnsiString('USA');
    aReader := aCmnd.ExecuteQuery;

    while aReader.Next do
    Writeln( aReader.Value['Country'].GetAnsiString );

    // Commit transaction
    aDBXConn.CommitFreeAndNil(aDBXTrans);

    Readln;
    aReader.Free;
    aCmnd.Free;
    aDbxConn.Free;
    end;
    end.

    Percebam que a possibilidade de nomear um parâmetro pela propriedade TDBXParameter.Name abre caminho para que haja controle por nome dos parâmetros sem necessidade de se criar class helpers.

    Responder
    • Bruno
      Bruno says:

      Como eu faria a passagem de parâmetro para essa situação, sendo que o primeiro e o terceiro parâmetros são CAMPO_DA_TABELA e o segundo e quarto são um valor qualquer:

      UPDATE EVENTOS SET ? = ? WHERE ? = ?

      Responder
      • Andreano Lanusse
        Andreano Lanusse says:

        Bruno, o dbExpress não suporta campo como parâmetro e sim os valores, neste caso você teria que fazer um FindReplace para substituir o nome do campo que foi passado como parâmetro

        Responder
    • Jáder Medeiros
      Jáder Medeiros says:

      A solução do Rafael criando os parâmetros manualmente realmente dá certo, mas onde está a criação dinâmica dos parâmetros quando executamos o método Prepare?

      Acredito que o parser do SQL em banco de dados SQL Server é diferente do parser em FireBird pois o comportamento é diferente. Em FireBird é tudo maravilha, mas quem trabalha com SQL Server não funciona.

      Alguém sabe se existe uma correção para isso?

      Responder

Trackbacks & Pingbacks

  1. […] This post was mentioned on Twitter by Embarcadero Tech, Andreano Lanusse. Andreano Lanusse said: Blog post: Como utilizar parâmetros em dbExpress (DBX) Framework http://bit.ly/bDVkdk […]

Deixe uma resposta

Want to join the discussion?
Feel free to contribute!

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *


This site uses Akismet to reduce spam. Learn how your comment data is processed.