Как я могу сделать это в Delphi? - PullRequest
1 голос
/ 09 марта 2009

Я конвертирую приложение из BDE в ADO.

В BDE, если запрос был открыт и вы назвали Sql.Clear, он автоматически закроет набор данных.

Однако это не относится к TADOQuery, где возникает исключение «Операция не может быть выполнена с закрытым набором данных».

Большая часть нашего унаследованного кода опирается на старое поведение BDE, поэтому я получаю множество ошибок времени выполнения из кода, подобного приведенному ниже.

Я хочу переопределить метод Sql.Clear моего класса TADOCustomQuery, чтобы он включал команду ".Close". Как я могу это сделать?

Метод .Clear находится в свойстве SQL, которое имеет тип TWideStrings. Мой реальный вопрос: как я могу переопределить метод TWideStrings.Clear на потомке TADOQuery?

У меня уже есть настроенный компонент TADOQuery, с этим для свойства SQL:

property SQL: TWideStrings read GetSQL write SetSQL;

Вот код, демонстрирующий мою проблему:

procedure TForm1.btnBDEDemoClick(Sender: TObject);
var
  qryBDE: TQuery;
begin
  //Both queries complete with no problem
  qryBDE := TQuery.Create(nil);
  try
    with qryBDE do begin
      DatabaseName := 'Test';  //BDE Alias
      Sql.Clear;
      Sql.Add('SELECT SYSDATE AS CURDAT FROM DUAL');
      Open;
      ShowMessage('the current date is: ' + FieldByName('CURDAT').AsString);

      Sql.Clear;  //<<<<<NO ERRORS, WORKS FINE
      Sql.Add('SELECT SYSDATE-1 AS YESDAT FROM DUAL');
      Open;
      ShowMessage('And yesterday was: ' + FieldByName('YESDAT').AsString);
    end;  //with qryBDE
  finally
    FreeAndNil(qryBDE);
  end;  //try-finally
end;

procedure TForm1.btnADODemoClick(Sender: TObject);
const
  c_ConnString = 'Provider=OraOLEDB.Oracle.1;Password=*;'+
    'Persist Security Info=True;User ID=*;Data Source=*';
var
  adoConn: TADOConnection;
  qryADO: TADOQuery;
begin
  //First query completes, but the second one FAILS
  adoConn := TADOConnection.Create(nil);
  qryADO := TADOQuery.Create(nil);
  try
    adoConn.ConnectionString := c_ConnString;
    adoConn.Connected := True;
    with qryADO do begin
      Connection := adoConn;
      Sql.Clear;
      Sql.Add('SELECT SYSDATE AS CURDAT FROM DUAL');
      Open;
      ShowMessage('the current date is: ' + FieldByName('CURDAT').AsString);

      Sql.Clear;//<<<<<<<<===========ERROR AT THIS LINE
      Sql.Add('SELECT SYSDATE-1 AS YESDAT FROM DUAL');
      Open;
      ShowMessage('And yesterday was: ' + FieldByName('YESDAT').AsString);
    end;  //with qryADO
  finally
    FreeAndNil(qryADO);
    FreeAndNil(adoConn);
  end;  //try-finally
end;

Ответы [ 6 ]

6 голосов
/ 10 марта 2009

Это не было функцией BDE как таковой. Если вы посмотрите на исходный код, поставляемый с Delphi, то увидите, что описанное вами поведение реализовано в методе SetQuery TQuery.SQL:

procedure TQuery.SetQuery(Value: TStrings);
begin
  if SQL.Text <> Value.Text then
  begin
    Disconnect;
    SQL.BeginUpdate;
    try
      SQL.Assign(Value);
    finally
      SQL.EndUpdate;
    end;
  end;
end;

В то время как SetQuery TADOQuery просто:

procedure TADOQuery.SetSQL(const Value: TWideStrings);
begin
  FSQL.Assign(Value);
end;

Почему Borland / Codegear решили не реализовывать это то же самое, что мне не под силу. Реализация SetQuery в TQuery в вашем собственном ADOQuery должна дать вам желаемое поведение.

5 голосов
/ 10 марта 2009

Проблема в том, что ваш набор данных открыт, когда вы выпускаете очистку. Для набора данных ADOD свойство связывается с обновлением базового набора данных ADO, и когда оно изменяется с открытым набором данных, возникает исключение.

Все, что вам нужно сделать, это закрыть набор данных прямо перед очисткой, и все будет работать правильно.

with qryADO do 
  begin      
    Connection := adoConn;      
    Sql.Clear;      
    Sql.Add('SELECT SYSDATE AS CURDAT FROM DUAL');      
    Open;      
    ShowMessage('the current date is: ' + FieldByName('CURDAT').AsString);
    qryADO.close; // <=== line added to close the database first.
    Sql.Clear;     
    Sql.Add('SELECT SYSDATE-1 AS YESDAT FROM DUAL');      
    Open;      
    ShowMessage('And yesterday was: ' + FieldByName('YESDAT').AsString);    
  end;  //with qryADO

EDIT В качестве альтернативы вы можете создать новый метод формы с именем SQLCLEAR, который выглядит следующим образом:

function TYourFormOrDataModule.SqlClear;
begin
  qryAdo.Close;
  qryAdo.Sql.Clear;
  qryBde.Sql.Clear;
end;

, а затем выполните поиск и замените «SQL.Clear» на «SqlClear». Но я предпочитаю метод выполнения закрытия в моем первоначальном ответе, так как он более последовательный и будет намного легче поддерживать долгосрочную перспективу. Использование такого инструмента, как gExperts, чтобы найти все экземпляры Sql.Clear и вставить qryAdo.Close до того, как оно станет тривиальным ... даже если есть несколько сотен экземпляров.

2 голосов
/ 10 марта 2009

Обновление

Я реализовал решение Скамрадта, написав небольшую утилиту для автоматического обновления всего нашего исходного кода. Это сработало так:

1 - Рекурсивно получить список всех файлов .PAS в папках нашего проекта

2 - применить эту процедуру ко всем этим файлам:

procedure ApplyChange(filename: string);
const
  c_FindThis = 'SQL.CLEAR';
var
  inputFile, outputFile: TStringList;
  i, postn, offset: integer;
  newline: string;
begin
  inputFile := TStringList.Create;
  outputFile := TStringList.Create;
  offset := 0;
  try
    inputFile.LoadFromFile(filename);
    outputFile.Assign(inputFile);  //default: they are the same

    for i := 0 to inputFile.Count - 1 do begin
      {
      whenever you find a "Sql.Clear", place a new line before it,
      which consists of everything up to the "Sql.Clear" (which may
      just be whitespace), plus the "Close" command.
      //}
      postn := Pos(c_FindThis,Uppercase(inputFile[i]));
      if (0 < postn) then begin
        newline := Copy(inputFile[i],1,postn-1) + 'Close;';
        outputFile.Insert(i+offset,newline);
        Inc(offset);
      end;
    end;

    //overwrite the existing file with the revised one
    outputFile.SaveToFile(filename);
  finally
    FreeAndNil(inputFile);
    FreeAndNil(outputFile);
  end;  //try-finally
end;
2 голосов
/ 09 марта 2009

Какую версию Delphi вы используете? Я попытался повторить это, но в Delphi 2009 он отлично работает - ошибок нет, и он возвращает ожидаемые данные.

спасибо дон

2 голосов
/ 09 марта 2009

Конечно, вы можете создать подкласс TAdoQuery, который перезаписывает метод Clear. Но я думаю, что это плохая практика.

Лучше изменить все запросы. Это может быть какая-то работа, но в конце концов она окупается.

Если вы посмотрите на пример TAdoQuery в справке:

ADOQuery := TADOQuery.Create(Self);
ADOQuery.Connection := ADOConn;
ADOQuery.SQL.Add(SQLStr);

{ Update the parameter that was parsed from the SQL query: AnId }
Param := ADOQuery.Parameters.ParamByName('AnId');
Param.DataType := ftInteger;
Param.Value := 1;

{ Set the query to Prepared - will improve performance }
ADOQuery.Prepared := true;

try
  ADOQuery.Active := True;
except
  on e: EADOError do
  begin
    MessageDlg('Error while doing query', mtError,
                [mbOK], 0);

    Exit;
  end;
end;

Вы видите, что это немного отличается от версии BDE.

Так что, вероятно, лучше создать функцию ExecuteSQL (сначала для BDE), а затем переписать ее для использования ADO.

1 голос
/ 10 марта 2009

Вместо вас я бы поступил так: посоветуйтесь с кем-нибудь, у кого есть D2009, действительно ли поведение зафиксировано, как говорит Дон - например отправьте ему (или другому, у которого есть D2009) контрольный пример. Если поведение в D2009 исправлено, то проблема проста.

Скопируйте ADODB.pas в каталог вашего проекта. Измените файл, чтобы иметь желаемое поведение (например, изменить метод SetSQL). Перекомпилируйте. Он должен работать. Это даст вам время для возможного обновления до D2009, когда вы сможете удалить старые, настроенные ADODB.pas из вашего проекта.

НТН.

...