Может ли dbExpress использовать TSQLQuery? как параметры?

Мы переносим код на Delphi XE2, и нам нужно изменить наши компоненты доступа к данным со стороннего ODBCExpress, который больше не работает, на TSQLQuery от dbExpress.

У нас есть параметризованный SQL-запрос, такой как:

sSQL :=
  'UPDATE ZTestData SET '+
  ' StringField =?, '+
  ' IntField = ?, '+
  ' DecimalField = ?, '+
  ' BooleanField = ?, '+
  ' DateTimeField = ?, '+
  ' TextField = ? '+
  ' WHERE UniqueID = 3';

если мы используем следующий код:

var
  qry:TSQLQuery;
begin
  qry.Close;
  qry.SQL.Text := sSQL;
  ShowMessage(IntToStr(qry.Params.Count));
end;

Он возвращает 0, поэтому мы не можем заставить работать привязки, но если мы изменим sSQL на:

sSQL :=
  'UPDATE ZTestData SET '+
  ' StringField =:Param1, '+
  ' IntField = :Param2, '+
  ' DecimalField = ?, '+
  ' BooleanField = ?, '+
  ' DateTimeField = ?, '+
  ' TextField = ? '+
  ' WHERE UniqueID = 3';

Он возвращает 2.

Будет очень сложно изменить все SQL-запросы на новый синтаксис параметров. Есть ли способ, которым TSQLQuery распознает ? синтаксис?

Я вижу, что DBXCommon.TDBXCommand использует ? синтаксис:

http://www.andreanolanusse.com/en/parameterized-queries-with-dbexpress-dbx-framework/

Но это означало бы отказ от нашего кода, использующего TSQLQuery. Какой самый быстрый/простой способ решить эту проблему? В чем разница между TSQLQuery и TDBXCommand с точки зрения того, что мне важно?


person Robo    schedule 14.02.2012    source источник


Ответы (3)


Я думаю, что самый быстрый подход — использовать помощники классов, которые будут реализовывать эту функциональность примерно так:

type
  TMyParamsHelper = class Helper for TSQLQuery
  public
    function SetupParams(AParamList: array of Variant): Boolean; overload;
    function SetupParams(ASQL: string; AParamList: array of Variant): Boolean; overload;
  end;

// implementation

function TMyParamsHelper.SetupParams(AParamList: array of Variant): Boolean;
var
  Index: Integer;
begin
  // here you can process the SQL as text and replace each ?
  // with :paramINDEX
  // first occurence of ? will be :param0, second will be :param1, etc.
  // implement your replace algorithm before the "for loop"
  for Index := Low(AParamList) to High(AParamList) do
    ParamByName(Format(':param%d', [Index])).AsVaraint := AParamList[ Index ];
  // of course you need to do it in a try...except block and return TRUE|FALSE
end;

function TMyParamsHelper.SetupParams(ASQL: string; AParamList: array of Variant): Boolean;
begin
  SQL.Text := ASQL;
  Result := SetupParams( AParamList );
end;

Итак, теперь все, что вам нужно сделать, это позвонить:

...
ASQLQueryVariable.SetupParams([2012, 'Hello World', 2.14, 'It WORKS!']);
// or
ASQLQueryVariable.SetupParams(
  'UPDATE MyTable SET Year = ?, Title = ?, Cents = ?, Comment = ? WHERE <CLAUSE HERE>',
  [2012, 'Hello World', 0.02, 'It WORKS!']
);
...

Примечание. Я пишу это наобум, могут быть опечатки и, возможно, это не лучший подход...

Дайте мне знать, как это работает для вас, я всегда хотел "?" вместо ParamByName, но было лень его реализовывать...

person Community    schedule 14.02.2012
comment
Смотрите мой ответ. DB.TParams.ParseSQL — это метод, который преобразует параметры в вопросительные знаки, если вы хотите пойти по этому пути. Я сам предпочитаю ParamByName. - person Robo; 17.02.2012

Нетривиальный подход:

  • подкласс TMyQuery из TSQLQuery;
  • в конструкторе TMyQuery задайте TStringList(SQL).OnChange свой собственный метод QueryChanged. См. TSQLQuery.QueryChanged в модуле SqlExpr.pas для получения подробной информации о том, что он делает.
  • там вам нужно будет заменить вызов SetParamsFromSQL своим собственным, который будет анализировать текст SQL и создавать объект параметра для каждого '?' возникновение.

Более простой подход:

  • создать процедуру, которая получит строку SQL и коллекцию Params;
  • процедура будет анализировать текст SQL и создавать объект параметра для каждого '?' возникновение;
  • установите для TSQLQuery.ParamCheck значение False и вызовите процедуру после установки свойства SQL.

Наконец, рассмотрите возможность использования сторонних решений, таких как AnyDAC. Он поддерживает ODBC и '?' маркеры параметров.

person da-soft    schedule 14.02.2012

В итоге я написал метод для преобразования вопросительных знаков в запросе в параметры стиля :param1. Интересно, что в Delphi есть метод DB.TParams.ParseSQL, который преобразует параметры в вопросительные знаки. Этот метод в основном противоположен этому.

function THstmt.AddParamsToSQL(const SQL: String): String;
var
  LiteralChar: Char;
  CurPos, StartPos, BeginPos: PChar;
  ParamCount:Integer;
begin
  //Locates the question marks in an SQL statement
  //and replaces them with parameters.
  //i.e. the reverse of DB.TParams.ParseSQL

  //This method is base on DB.TParams.ParseSQL

  //For example, given the SQL string
  //SELECT * FROM EMPLOYEES WHERE (ID = ?) AND (NAME = ?)

  //ParseSQL returns the string
  //SELECT * FROM EMPLOYEES WHERE (ID = :1) AND (NAME = :2)

  Result := '';

  ParamCount := 0;
  StartPos := PChar(SQL);
  BeginPos := StartPos;
  CurPos := StartPos;
  while True do
  begin
    // Fast forward
    while True do
    begin
      case CurPos^ of
        #0, '?', '''', '"', '`':
          Break;
      end;
      Inc(CurPos);
    end;

    case CurPos^ of
      #0: // string end
        Break;
      '''', '"', '`': // literal
      begin
        LiteralChar := CurPos^;
        Inc(CurPos);
        // skip literal, escaped literal chars must not be handled because they
        // end the string and start a new string immediately.
        while (CurPos^ <> #0) and (CurPos^ <> LiteralChar) do
          Inc(CurPos);
        if CurPos^ = #0 then
          Break;
        Inc(CurPos);
      end;
      '?': //parameter
      begin
        Inc(CurPos);
        Inc(ParamCount);
        Result := Result + Copy(SQL, StartPos - BeginPos + 1, CurPos - StartPos - 1) + ':' + IntToStr(ParamCount);
        StartPos := CurPos;
      end;
    end;
  end;
  Result := Result + Copy(SQL, StartPos - BeginPos + 1, CurPos - StartPos);
end;
person Robo    schedule 16.02.2012