2009-03-09 2 views
1

Я конвертирую приложение из BDE в ADO.Как я могу сделать это в Delphi?

Под 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; 

ответ

5

Проблема заключается в том, что ваш набор данных открыт, когда вы выдаете ясном. Для ADODataset свойство подключено для обновления базового набора данных 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, прежде чем он станет тривиальным ... даже если есть несколько сотен экземпляров.

+0

Да, я знаю, что ... но я надеюсь избежать добавления этой строки в 100 существующих мест в коде. – JosephStyons

2

Конечно, вы можете создать подкласс 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.

+0

Ну, может быть, я Здесь плотно, но метод Clear имеет свойство SQL, которое имеет тип TWideStrings. Как я могу переопределить метод TWideStrings.Clear для потомка TADOQuery? Возможно, это должен был быть мой реальный вопрос .... – JosephStyons

+0

Извините, неверно сформулировал вопрос. Нет, вы не можете переопределить Clear. Но с другой стороны, он не должен давать сообщение об ошибке, так что что-то еще не так. –

2

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

благодаря дон

+0

Я использую Delphi 2007 и Delphi 5, которые имеют ту же проблему. Мне интересно, что D2009 не имеет такой же проблемы. – JosephStyons

6

Это не было особенностью БДЭ как таковой. Если вы посмотрите на исходный код, который поставляется с 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 должно дать вам поведение, которое вы желаете.

+0

Вы правы, и я тоже посмотрел на этот код. Тем не менее, изменение моего пользовательского ADO SetQuery делает это так же, как TQuery не помогает (я использовал точно такой же код, за исключением «Close» вместо «Disconnect») – JosephStyons

1

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

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

HTH.

2

Update

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

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;