Announcement

Collapse
No announcement yet.

Direkter Zugriff auf die COM-Objekte von ADO

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • Direkter Zugriff auf die COM-Objekte von ADO

    Hallo,

    wir entwickeln hochverfügbare Programme im Automotiveumfeld. Um Datenbankzugriffe sowohl auf Oracle, wie auch auf den MS-SQL-Server abzudecken, sind alle Datenzugriffe in Cobol-Dlls gekapselt. Die Serverprozesse sind ebenfalls in Cobol geschrieben. Bei den eingesetzten Servern handelt es sich gewöhnlich um geclusterte vier bis acht Prozessorenrechner unter Windows2000.

    Meine Aufgabe besteht nun darin zu prüfen, ob mit Delphi in Verbindung mit COM-Objekten von ADO ebenso performate und stabile Aplikationen geschrieben werden können wie bisher unter Cobol in Verbindung mit dem SQL-Precompiler.

    Meine Erfahrung mit Delphi ist ziemlich angestaubt und beschränkt sich weitesgehend auf die Komponentenentwicklung unter Delphi 3.
    Deshalb meine Frage:
    Gibt es eine Möglichkeit „reinrassig“ mit den COM-Objekte von ADO auf Tabellen zuzugreifen, Feldinhalte auszulesen und diese weiterzuverarbeiten?
    (Andreas Kosch hat am 12.07.2001 im Formum „Reinrassige ADO-Objekte“ ein sehr gutes UPDATE – Beispiel beschrieben).

    Wie würde der Syntax für

    SELECT Feld1-int, Feld2-char, Feld2-varchar FROM Test WHERE Feld3-int = 4711

    aussehen? Wie würde der Syntax aussehen, wenn ich den Inhalt von Feld1-int weiter auswerten möchte?

    Ich bin dankbar für jeden Tipp.

    Mit freundlichen Grüßen
    Konrad Hötschl

  • #2
    Hallo,

    das folgende Beispiel demonstriert, wie man in einem eigenen Thread über die nativen ADO-Objekte eine Stored Procedure aufruft und dabei einen Parameter übergibt und das Ergebnis hinterher ausliest. Immer dann, wenn Parameter im Spiel sind, wird eine Instanz des <b>Command</b>-Objekts von ADO benötigt. Im Fall einer aufgerufenen Stored Procedure werden der Rückgabewert der SP sowie alle OUTPUT-Parameter ebenfalls über die Parameters-Kollektion zurückgeliefert:
    <pre>
    unit Step4_SoundexThread;

    interface

    uses
    Classes, Windows;

    type
    TSoundexNameDiff = class(TThread)
    private
    { Private-Deklarationen }
    FCS : String;
    FName : String;
    FThreadID : Integer;
    procedure InfoUser;
    protected
    procedure Execute; override;
    public
    constructor Create(const sCS, sName: String);
    end;

    implementation

    uses SysUtils, ActiveX, ADOInt, Variants;

    { SoundexNameDiff }

    constructor TSoundexNameDiff.Create(const sCS, sName: String);
    begin
    inherited Create(True);
    FCS := sCS;
    FName := sName;
    FreeOnTerminate := True;
    Resume;
    end;

    procedure TSoundexNameDiff.Execute;
    var
    aConnection : _Connection;
    aCommand : _Command;
    aParam : _Parameter;
    vName : OleVariant;
    vRowsAffected: OleVariant;
    iCount : Integer;
    begin
    FThreadID := GetCurrentThreadID;
    vName := FName;
    CoInitialize(nil);
    try
    aConnection := CoConnection.Create;
    aConnection.Open(FCS, '', '', adOpenForwardOnly);
    try
    aCommand := CoCommand.Create;
    try
    aCommand.CommandType := adCmdStoredProc;
    aCommand.Set_CommandText('stprSoundexName1Diff');
    // Rückgabewert
    aParam := aCommand.CreateParameter('RETURN_VALUE', adInteger,
    adParamReturnValue,4, EmptyParam);
    aCommand.Parameters.Append(aParam);
    // Input-Parameter: Zu suchender Name
    aParam := aCommand.CreateParameter('@sNAME1', adVarChar,
    adParamInput, 40, vName);
    aCommand.Parameters.Append(aParam);
    //
    aCommand.Set_ActiveConnection(aConnection);
    aCommand.Execute(vRowsAffected, EmptyParam, adExecuteNoRecords);
    iCount := aCommand.Parameters.Item[0].Value;
    if iCount > 0 then
    Synchronize(InfoUser);
    finally
    aParam := nil;
    aCommand := nil;
    end;
    finally
    aConnection.Close;
    aConnection := nil;
    end;
    finally
    CoUninitialize;
    end;
    // akustische Rückmeldung über das Thread-Ende
    MessageBeep($FFFFFFFF);
    end;

    procedure TSoundexNameDiff.InfoUser;
    var
    sMsg : String;
    begin
    sMsg := 'DIFFERENCE hat Treffer gefunden.' + #13 +
    'Main-ThreadID: ' + IntToStr(GetCurrentThreadID) + #13 +
    'Difference-ThreadID: ' + IntToStr(FThreadID);
    Windows.MessageBox(0, PChar(sMsg), PChar('Thread-Abfrage'),
    MB_OK or MB_ICONINFORMATION);
    end;
    end.
    </pre>
    Das zweite Beispiel für eine ACCESS-Datenbank verwendet ein eigenen Command-Objekt für eine <b>parametisierte SELECT-Abfrage</b>. Der Parameter wird dabei über ein eigenes Parameter-Objekt übergeben. Die Eigenschaften des Parameters können dabei - inklusive eines Vorgabewertes - exakt definiert werden. Dabei kann die Zuordnung entweder über die Parameterposition - oder übersichtlicher - über einen Parameternamen erfolgen:
    <pre>
    procedure TFormMain.ButtonOpenCommandClick(Sender: TObject);
    resourcestring
    cCOMMANDTEXT = 'Parameters [AR] Long;' +
    'SELECT * FROM Country WHERE Country.Area>=[AR]';
    var
    aConnection : _Connection;
    aRecordset : _Recordset;
    aCommand : _Command;
    aParameter : _Parameter;
    swConnString: WideString;
    swData : WideString;
    vParam : OleVariant;
    iRecCount : Integer;
    vRowsAffect : OleVariant;
    begin
    MemoCountry.Lines.Clear;
    StatusBar1.SimpleText := 'Recordset für Command wird geöffnet....';
    // Step 1: Connection-Objekt
    aConnection:= CoConnection.Create;
    swConnString := 'Provider=Microsoft.Jet.OLEDB.4.0;' +
    'Data Source=C:\Database\dbdemos.mdb;' +
    'Persist Security Info=False';
    aConnection.Open(swConnString, '', '', adConnectUnspecified);
    // Step 2: Command-Objekt
    aCommand := CoCommand.Create;
    aCommand.Set_ActiveConnection(aConnection);
    aCommand.CommandType := adCmdText;
    aCommand.Set_CommandText(cCOMMANDTEXT);
    // Step 3: Parameter-Objekt
    vParam := 1000; // Vorgabewert des Parameters
    aParameter := aCommand.CreateParameter('[AR]', adInteger, adParamInput, 4, vParam);
    aCommand.Parameters.Append(aParameter);
    // Vorgabewert des Parameters überschreiben
    aParameter.Value := 3002000;
    aCommand.Execute(vRowsAffect, vParam, 0);
    // Step 4: Recordset-Objekt
    aRecordset := CoRecordSet.Create;
    try
    aRecordset.CursorType := adOpenKeyset;
    aRecordset.LockType := adLockOptimistic;
    // Step 5: Recordset über das Command-Objekt aufbauen
    aRecordset.Open(aCommand, EmptyParam, adOpenStatic,
    adLockReadOnly, adCmdText);
    iRecCount := aRecordset.Get_RecordCount;
    swData := aRecordset.GetString(adClipString, iRecCount, '; ', #13#10,'(NULL)');
    MemoCountry.Lines.Add(swData);
    finally
    aRecordset.Close;
    end;
    StatusBar1.SimpleText := 'Recordset für Command ist geöffnet.';
    end;

    end.
    </pre>
    Die Syntax "Parameters [AR] Long;SELECT * FROM Country WHERE Country.Area>=[AR]" ist einer der vielen ADO-Sonderfälle, die auch noch Provider-spezifisch sind (hier Microsoft Jet Engine).

    Wenn der direkte Zugriff auf jedes einzelne Feld der RecordSet-Datenmenge benötigt wird, kann man folgendes machen:
    <pre>
    ...
    aRS.Open(aCommand, EmptyParam, adUseClient,
    adLockBatchOptimistic, adOptionUnspecified);

    // TStringGrid-Beschriftung
    for iCol := 0 to 2 do
    StringGrid1.Cells[iCol, 0] := aRS.Fields[iCol].Name;
    // TStringGrid mit den Daten vom Recordset füllen
    iRow := 1;
    repeat
    for iCol := 0 to 2 do
    StringGrid1.Cells[iCol, iRow] := aRS.Fields[iCol].Value;
    aRS.MoveNext;
    Inc(iRow);
    until aRS.EOF;

    ...
    </pre>
    Das Beispiel liest die Daten aus und stellt diese in einem <b>TStringGrid</b> (kein TDBGrid) dar

    Comment


    • #3
      Danke für die umgehende Antwort.
      Das Beispiel bringt mich ein ganze Stück weiter.

      Konrad Hötsch

      Comment


      • #4
        Hallo Herr Kosch,

        basierend auf Ihr Beispiel habe ein kleines Programm geschrieben. Bei der Auswertung konnte ich beobachten, dass für jeden Zugriff ein Cursor geöffnet wurde und zwar unabhängig von der Treffermenge.

        Meine Frage nun:
        Wie muss ich es „anstellen“, das ich eine SELECT-Statement (keinen Cursor) absetzen kann und anschließend sowohl die Felder auslesen wie auch den SQL-Code (SQL-OK, -Nodata, -Moredata, -Duplicate, -Deadlock, usw. ) auswerten kann.

        Vielen Dank für jeden Tip.

        Mit freundlichen Grüßen
        Konrad Hötsch

        Comment


        • #5
          Hallo,

          wie dringend ist das Problem? In der nächsten Ausgabe von DER ENTWICKLER erscheint ein weiterer Artikel zum Thema ADO von mir, der sich auf 8 Seiten ausschliesslich um das Thema ADO, Cursor und automatischer OLE DB Datenbankverbindungs-Pool beschäftigt. Je nach verwendetem OLE DB Provider, CursorLocation-Wert, CursorType-Wert und LockType-Wert erhält man unterschiedliche Ergebnisse, wobei dies bei einigen Providern (wie zum Beispiel beim Microsoft OLE DB Provider for SQL Server) auch noch davon abhängt, ob in der angesprochenen Tabelle eine TIMESTAMP-Spalte vorhanden ist oder nicht. Also hat auch das Datenmodell der Datenbank an dieser Stelle Auswirkungen auf das Verhalten. Die Zusammenhänge werden zwar von Microsoft im MDAC-SDK ausführlich dokumentiert, aber für eine ernsthafte Antwort auf Ihre Frage müsste ich jetzt viel zu viel Text eintippen :-)

          Generell lassen sich die folgenden Aussage treffen: Die Verbindung zum Microsoft SQL Server 7/2000 ist dann am schnellsten/effektivsten, wenn eine der beiden folgenden Situationen zutrifft:

          1. <b>clUseClient</b> fordert eine überschaubare Datenmenge an, der OLE DB Provider setzt automatisch den sogenannten <i>Firehouse-Cursor</i> ein.

          2. Die Kombination CursorLocation := clUseServer + CursorType := ctOpenForwardOnly + LockType := ltReadOnly wird verwendet. Auch hier greift OLE DB implizit auf den <i>Firehouse-Cursor</i> zurück.

          Der Datenbankverbindungs-Pool von OLE DB konfiguriert sich automatisch und dynamisch jederzeit selbst, es ist also normal, dass nach dem Schliessen der Objekte noch einige Verbindungen offen bleiben (die werden jedoch bei der nächsten Anforderung wiederverwendet oder nach der Timeout-Zeitspanne automatisch geschlossen).

          Für die Abfrage der Fehlernummern gibt es zwei Möglichkeiten:

          1. Explizit nachfragen:
          <pre>
          procedure TForm1.ButtonRaiseError2aClick(Sender: TObject);
          var
          iError : Integer;
          sError : String;
          begin
          case RadioGroup1.ItemIndex of
          0 : ShowMessage('Es ist keine Connection aktiv.');
          1 : ADOStoredProcMSDASQL2.ExecProc;
          2 : begin
          ADOStoredProcSQLOLEDB2.ExecProc;
          with ADOConnectionSQLOLEDB do
          for iError := 0 to Errors.Count - 1 do
          sError := sError + Errors[iError].Description + #10#13;
          ShowMessage(sError);
          end;
          end;
          end;
          </pre>
          2. PostError auswerten
          <pre>
          procedure TForm1.ADODataSet1PostError(DataSet: TDataSet; E: EDatabaseError;
          var Action: TDataAction);
          var
          i : Integer;
          s : String;
          begin
          if E is EADOError then
          with E as EADOError do
          MemoLog.Lines.Add(e.Message);
          with ADOConnection1 do
          for i:= 0 to Errors.Count - 1 do
          begin
          s := Format('Source: %s', [Errors[i].Source]);
          MemoLog.Lines.Add(s);
          s := Format('NativeError: %d', [Errors[i].NativeError]);
          MemoLog.Lines.Add(s);
          s := Format('%s; (SQLState: %s)',
          [Errors[i].Description, Errors[i].SQLState]);
          MemoLog.Lines.Add(s);
          MemoLog.Lines.Add(' --- ');
          end;
          Action := daAbort;
          end;
          </pre>
          Über <b>NativeError</b> und <b>SQLState</b> kommt man an alle die Informationen, die vom SQL Server bereitgestellt werden. Auch die ADO Express-Komponenten greifen an dieser Stelle über <b>Errors</b> direkt auf das native Error-Objekt von ADO zu

          Comment


          • #6
            Hallo Herr Kosch,

            erste einmal vielen Dank für Ihre ausführliche Antwort.

            Mein Problem ist insofern nicht dringlich, da es sich dabei um ein eruieren einer neuen Entwicklungsumgebung handelt und ich die nächste Ausgabe von DER ENTWICKLER. abwarten kann.

            MfG

            Konrad Hötsch

            Comment

            Working...
            X