2009-03-09 12 views
1

Ich konvertiere eine Anwendung von BDE zu ADO.Wie kann ich das in Delphi machen?

Wenn unter der BDE eine Abfrage geöffnet war und Sie "Sql.Clear" aufgerufen haben, wurde das Dataset automatisch geschlossen.

Dies ist jedoch nicht der Fall unter TADOQuery, wo es eine Ausnahme erhöhen wird „Operation kann nicht auf einem geschlossenen Datensatz durchgeführt werden“.

Viele unserer Legacy-Code basiert auf dem alten BDE Verhalten, so dass ich eine Menge von Laufzeitfehlern von Code wie das Beispiel unten.

Ich mag die Sql.Clear Methode meiner TADOCustomQuery Klasse außer Kraft zu setzen, so dass es einen „.Close“ Befehl umfasst. Wie kann ich das machen?

Die „.Clear“ Methode ist auf der SQL-Eigenschaft, die vom Typ TWideStrings ist. Meine eigentliche Frage ist: Wie kann ich die TWideStrings.Clear-Methode für einen Nachkommen von TADOQuery überschreiben?

Ich habe eine maßgeschneiderte TADOQuery Komponente bereits mit dieser für die SQL-Eigenschaft:

property SQL: TWideStrings read GetSQL write SetSQL; 

Hier einige Code wird das Problem, das ich habe zu demonstrieren:

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; 

Antwort

5

Das Problem ist, dass Ihre Datenmenge geöffnet ist, wenn Sie die klare ausstellen. Für das ADODataset ist die Eigenschaft verkabelt, um das zugrunde liegende ADO-Dataset zu aktualisieren, und wenn es sich bei geöffnetem Dataset ändert, wird die Ausnahme ausgelöst.

Alles, was Sie tun müssen, ist ein Datensatz direkt vor Ihrem Löschen schließen und es wird alles ordnungsgemäß funktionieren.

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 Als Alternative Sie ein neues Formular Methode namens SQLCLEAR schaffen könnte, die wie folgt aussieht:

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

und dann eine Suche und ersetzen, um für „Sql.Clear“ "SqlClear".Aber ich bevorzuge die Methode, das Schließen in meiner ursprünglichen Antwort durchzuführen, da es konsistenter ist und viel einfacher ist, langfristig zu erhalten. Verwenden Sie ein Tool wie gExperts, um alle Instanzen von Sql.Clear zu finden, und fügen Sie eine qryAdo.Close ein, bevor es trivial ist ... selbst wenn es ein paar hundert Instanzen gibt.

+0

Ja, ich weiß das ... aber ich hoffe, dass ich diese Zeile nicht zu 100s von existierenden Orten in Code hinzufügen kann. – JosephStyons

2

Natürlich Sie können eine Unterklasse von TAdoQuery erstellen, die die Clear-Methode überschreibt. Aber ich denke, es ist eine schlechte Übung.

Es ist besser, alle Abfragen zu ändern. Es ist vielleicht etwas Arbeit, aber am Ende zahlt es sich aus.

Wenn Sie am Beispiel TADOQuery schauen in der Hilfe:

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; 

Man sieht es ein bisschen mehr ist anders als die BDE-Version.

So ist es wahrscheinlich am besten eine ExecuteSQL Funktion (zunächst für die BDE) zu erstellen und dann neu schreiben sie von ADO verwendet werden.

+0

Nun, vielleicht ich bin hier dicht. aber die Clear-Methode ist auf der SQL-Eigenschaft, die vom Typ TWideStrings ist. Wie kann ich die TWideStrings.Clear-Methode für einen Nachkommen von TADOQuery überschreiben? Vielleicht sollte das meine eigentliche Frage gewesen sein .... – JosephStyons

+0

Entschuldigung, habe deine Frage falsch gelesen. Nein, Sie können das Löschen nicht überschreiben. Aber dann sollte es wieder die Fehlermeldung nicht geben, damit etwas anderes falsch ist. –

2

Welche Version von Delphi verwenden Sie? Ich habe versucht, dies zu replizieren, aber es funktioniert gut in Delphi 2009 - keine Fehler gemeldet und es gibt die Daten, die ich erwarte.

dank don

+0

Ich benutze Delphi 2007 und Delphi 5, die beide das gleiche Problem aufweisen. Ich finde es interessant, dass D2009 nicht das gleiche Problem hat. – JosephStyons

6

Dies war kein Merkmal der BDE an sich. Wenn Sie an der Quelle suchen, dass Schiffe mit Delphi werden Sie sehen, dass das Verhalten, das Sie beschrieben auf TQuery.SQL der SetQuery Methode implementiert:

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; 

Während TADOQuery der SetQuery ist einfach:

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

Warum Borland/Codegear hat beschlossen, es nicht zu implementieren, das ist mir nicht möglich. Das Implementieren von SetQuery von TQuery in Ihrer benutzerdefinierten ADOQuery sollte Ihnen das von Ihnen gewünschte Verhalten bieten.

+0

Sie haben Recht, und ich habe mir auch diesen Code angeschaut. Es ist jedoch nicht hilfreich, meine benutzerdefinierte ADO-SetQuery so zu ändern, dass sie genau wie TQuery funktioniert (ich habe genau den gleichen Code verwendet, mit Ausnahme von "Close" anstelle von "Disconnect"). – JosephStyons

1

Anstelle von Ihnen würde ich das tun: Fragen Sie jemanden, der D2009 hat, wenn das Verhalten wirklich behoben ist, wie Don sagt - für ex. Sende ihm (oder einem anderen, der D2009 hat) einen Testfall. Wenn das Verhalten in D2009 behoben ist, ist das Problem einfach.

Kopieren Sie die ADODB.pas in Ihr Projektverzeichnis. Ändern Sie die Datei, um das gewünschte Verhalten zu erhalten (z. B. die SetSQL-Methode ändern). Neu kompilieren. Es sollte funktionieren. Dies gibt Ihnen Zeit für ein eventuelles Upgrade auf D2009, wenn Sie die alten, angepassten ADODB.pas aus Ihrem Projekt entfernen können.

HTH.

2

aktualisieren

I skamradt-Lösung implementiert, indem ein kleines Programm zu schreiben automatisch unsere Quelle aller Code zu aktualisieren. Es funktionierte wie folgt aus:

1 - Recursively eine Liste aller .PAS Dateien in unserem Projektordner

2 erhalten - dieses Verfahren alle diese Dateien anwenden:

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;