Announcement

Collapse
No announcement yet.

Umstieg auf ADO

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

  • Umstieg auf ADO

    Hallo, nach langen hin und her hab ich nun die Genehmigung zu testen wie groß der Umstellungsaufwand unseres Produktes nach ADO ist. Jetzt habe ich folgendes Problem:

    Wenn ich eine MS Access97 Datenbank (mit Kennwort) öffnen will bekomme ich folgende Fehlermeldung:

    "Die Anwendung kann nicht gestartet werden. Die Informationsdatei für die Arbeitsgruppe fehlt oder ist exlcusiv von einem anderen Benutzer geöffnet."

    Außerdem hab ich da gleich noch eine weitere Frage:
    Welchen Nach/Vorteil habe ich wenn ich den Cursor Client/Serverseitig setzte.
    Was heißt das?

    MfG
    Mathias Fabian

  • #2
    Hallo,

    zur Frage 1: <br>
    Immer dann, wenn die MDB-Datei passwortgeschützt ist, muss beim ConnectionString auch das <b>Workgroup File</b> (*.MDW) angegeben werden.

    zur Frage 2: <br>
    Beim Zugriff auf eine ACCESS-Datenbank stellt sich die Frage nicht, da man in jedem Fall einen serverseitigen Cursor (<b>clUseServer</b>) verwenden sollte. ACCESS ist keine SQL-Datenbank, die Microsoft JET Engine verarbeitet alle Datensätze direkt auf dem eigenen Rechner, so dass ein zusätzlicher Puffer der OLE DB Client Cursor Engine (clUseClient) die Performance in den Keller drückt.

    P.S: In der nächsten Ausgabe von DER ENTWICKLER gehe ich in einem Beitrag ausführlich auf das Thema der ADO-Cursor ein

    Comment


    • #3
      Vielen Dank für die schnelle Antwort.

      Jetzt habe ich eine Frage zu Parameter.

      Bisher habe ich das wie folgt gemacht:

      qry1.sql.text := 'select Nummer from Auftrag where nummer =:PNummer';
      qry1.parambyname('PNummer').asstring := 'xyz';
      qry1.open;

      Wie kann ich das bei ADO machen??
      Ich meine es geht ja über

      ADOqry.parameters.parambyname('PNummer').value = 'xyz';

      Aber wie mache ich das wenn ich Zahlen oder Datumswerte übergeben will? Oder "weis" das Programm was der Parameter für einen Datentyp hat

      Comment


      • #4
        Hallo,

        der Ansatz mit TADOQuery ist schon falsch - von TADOQuery und TADOTable würde ich die Finger lassen :-)

        Die ADO Express-Komponenten von Borland sind nur VCL-Wrapperkomponenten für die nativen ADO-Objekte (COM-Objekte) von Microsoft. Und ADO kennt nicht TADOTable, TADOQuery oder TADODataSet, sondern nur die <i>Connection-</i>, <i>Command-</i> und <i>Recordset</i>-Objekte. Daher sollte man sich an die von ADO (Microsoft) aufgestellten Spielregeln halten. Dies bedeutet für uns, dass es immer dann einfach wird, wenn alle SQLs bereits im Objektinspektor zugewiesen und dort einmal die Eigenschaft <b>Parameters</b> angeklickt wird. Nur dann baut Delphi im Hintergrund für uns die von ADO erwarteten Hilfsobjekte (Parameter-Objektinstanzen der Parameters-Kollektion von ADO) automatisch auf (indem Delphi die ADO-Methode <b>Refresh</b> der Parameters-Kollektion einmal aufruft, damit die Kollektion in der Datenbank die Typen der Parameter sowie die sonstigen Eigenschaften ermittelt).

        In einem Beispiel sieht das etwa so aus:
        <pre>
        object ADODataSetNew: TADODataSet
        Connection = ADOConnection1
        CursorType = ctStatic
        CommandText =
        'select RecID, CatID, Thema, NewsText, NewsDate from BorlandNews where RecID = :RecID'
        Parameters = <
        item
        Name = 'RecID'
        Attributes = [paSigned]
        DataType = ftInteger
        Precision = 10
        Size = 4
        Value = -1
        end>
        Left = 208
        Top = 112
        object ADODataSetNewRecID: TAutoIncField
        FieldName = 'RecID'
        ReadOnly = True
        end
        object ADODataSetNewCatID: TIntegerField
        FieldName = 'CatID'
        end
        object ADODataSetNewThema: TStringField
        FieldName = 'Thema'
        Size = 75
        end
        object ADODataSetNewNewsText: TMemoField
        FieldName = 'NewsText'
        BlobType = ftMemo
        end
        object ADODataSetNewNewsDate: TDateTimeField
        FieldName = 'NewsDate'
        end
        object ADODataSetNewNewsCategory: TStringField
        FieldKind = fkLookup
        FieldName = 'NewsCategory'
        LookupDataSet = ADODataSetCategory
        LookupKeyFields = 'CatID'
        LookupResultField = 'Cat'
        KeyFields = 'CatID'
        Lookup = True
        end
        end
        </pre>
        Zur Laufzeit greift das Programm dann über <b>Parameters</b> (TADODataSet) auf die Parameters-Kollektion zu, um die Werte zuzuweisen:
        <pre>
        procedure TFormMain.DBGrid1CellClick(Column: TColumn);
        begin
        if ADODataSetSearch.State in [dsBrowse] then
        if ADODataSetSearchRecID.Value > 0 then
        with ADODataSetNew do
        begin
        Active := False;
        Parameters[0].Value := ADODataSetSearchRecID.Value;
        Active := True;
        end;
        end;
        </pre>
        Wenn das feste Zuweisen der SQLs im Objektinspektor nicht in Frage kommt, müsste man strengbetrachtet das nachbauen, was auch die Visual Basic-Leute tun, wenn sie direkt mit den nativen ADO-Objekten hantieren. Und dass sieht zur Abschreckung dann so aus:
        <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>
        &#10

        Comment


        • #5
          Danke für die Antwort,
          aber wieso kann ich den Commandtext (SQL) dann nicht zur Laufzeit zuweisen und parameters.refresh von hand aufrufen

          Comment


          • #6
            Hallo Andreas,<br>da habe ich aber etwas anderes festgestellt.<br>Ich muß eine Access (2000 o. 97) mit ca 200.000 Datensätzen scannen. D.h. ich öffne die Datenbank und lese jeden Datensatz. Mit clUseServer dauert die Sache ca 200-300 Windowsticks länger und die RecordCount Eigenschaft liefert nicht die Anzahl der Records.<br>Das ist der Code<pre>
            ...
            FADOConnection:=CoConnection.Create;
            FADOConnection.Open(ConnectionString,'','',-1);
            ...
            procedure TKoladata.ScannTable;
            var
            ADORecordset : RecordSet;
            Item : TDataItem;
            Counter : Integer;
            begin
            Counter:=0;
            If Assigned(FOnBeforeLoadItem) then
            FOnBeforeLoadItem(Self,0);
            ADORecordset:=CoRecordSet.Create;
            ADORecordset.Open(sSQL,FADOConnection,adUseClient, adLockReadOnly,-1);
            If Assigned(FOnBeforeLoadItem) then
            FOnBeforeLoadItem(Self,ADORecordset.RecordCount);
            Try
            While (Not ADORecordset.EOF) do
            begin
            Item:=FItems.Add;
            Item.GS:=ADORecordset.Fields.Item['GS'].Value;
            Item.KOST:=ADORecordset.Fields.Item['KOST'].Value;
            Item.FueB:=ADORecordset.Fields.Item['Fueb'].Value;
            Item.KOA:=ADORecordset.Fields.Item['KOA'].Value;
            Item.PLAN:=ADORecordset.Fields.Item['PLAN'].Value;
            If ADORecordset.Fields.Item['intern_extern'].Value='extern' then
            Item.IntExt:=ieExtern
            else
            Item.IntExt:=ieIntern;
            Inc(Counter);
            If Assigned(FOnLoadItem) then
            FOnLoadItem(Self,Counter);
            ADORecordset.MoveNext;
            end;
            Finally
            ADORecordset.Close;
            ADORecordset:=Nil;
            end
            end;
            </pre>Jens Schuman

            Comment


            • #7
              Hallo Jens,
              also bei mir dauert es wesentlich länger wenn ich ClUseClient einstelle

              Comment


              • #8
                Hallo Mathias,<br>natürlich hast Du recht. Ich habe hier nur unüberlegt rumgelabert. Den Unterschied zwischen clUseServer und clUseClient merke ich nicht, da die Zeit für die Abfrage im Vergleich mit der Zeit für's Einlesen der Daten verschwindent gering ist.<br>Jens Schuman

                Comment


                • #9
                  Hallo,

                  &gt;aber wieso kann ich den Commandtext (SQL) dann nicht zur Laufzeit zuweisen und parameters.refresh von hand aufrufen

                  selbstverständlich kann man das tun - wenn die unerwünschten Nebenwirkungen in Kauf genommen werden. Denn in diesem Fall verliert man die Performance-Vorteile eines Compilers, ADO muss dann quasi als "Interpreter" arbeiten und bei jedem Aufruf die Schritte zur automatischen Ermittlung der Datentypen jedesmal aufs Neue abarbeiten. Man produziert also immer dann unnötige Netzlast, wenn die Datenbank nicht auf dem eigenen Rechner zu finden ist.
                  &#10

                  Comment


                  • #10
                    Hallo,
                    ich hab jetzt das Problem das ich alles auf ClUseServer gestellt habe. Wenn ich jetzt ein Refresh mache bekomme ich folgende Meldung:
                    "Der aktuelle Provider unterstützt keine Aktualisierung der zugrundeliegenden Werte." Als Datenbank verwende ich Access97.
                    Wenn ich auf ClUserClient schalte erhalte ich den Fehler nicht, aber es dauert ewig bis er das Refresh durchführt hat (über 10 Sekunden).
                    Woran liegt das?

                    PS.: Herr Kosch, wann erscheint Ihr Buch über ADO und Delphi, bei einigen Onlineversendern steht "demnächst lieferbar".

                    Gruß
                    Mathia

                    Comment


                    • #11
                      Hallo,

                      zur Frage 1: <br>
                      Dieses Problem kann ich nicht nachvollziehen, der Aufruf von <b>Refresh</b> ist bei clUseServer problemlos sowohl mit einer ACCES97- als auch einer ACCESS2000-Datenbank möglich. Die TADODataSet-Instanz verwendet CommandType = cmdTableDirect.
                      <pre>
                      procedure TForm1.Button1Click(Sender: TObject);
                      begin
                      if ADODataSet1.CursorLocation = clUseServer then
                      MemoLog.Lines.Add('clUseServer: Ja');
                      ADODataSet1.Refresh;
                      end;
                      </pre>

                      zum Problem 2: <br>
                      Der Aufruf der Methode Refresh des Recordsets führt dazu, dass im ungünstigsten Fall für jeden (!) Datensatz eine einzelne SELECT-Abfrage zur Aktualisierung abgeschickt wird. Im Fall einer ACCESS-Datenbank gehe ich davon aus, dass die doppelte Pufferung über die OLE DB Client Cursor Engine der Grund der Verzögerung ist

                      Comment

                      Working...
                      X