Announcement

Collapse
No announcement yet.

TADODataSet.Open = "Zugriffsverletzung an Adresse..."

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

  • TADODataSet.Open = "Zugriffsverletzung an Adresse..."

    Hallo,<br>

    ich bin gerade dabei, eine Applikation von BDE (Paradox) auf ADO (Oracle8, MS-SQL2000, DB2 und SAP-DB) umzustellen. Mein Problem ist, dass es nahezu ständig zu Zugriffsverletzungen kommt, wenn ich mein Haupt-ADODataSet öffne.<br>

    Über das ADODataSet gewinne ich aus mehreren Tabellen eine Datenmenge, wobei die Spalten von der durch den User gewählten Sicht abhängig sind (Auswahl per TDBLookupComboBox). Beim Ändern der Sicht wird das erforderliche SQL-Statement neu erzeugt (im Beispiel "mein_SQL_Statement"). Zusätzlich werden noch Calculated-Fields dem ADODataSet hinzugefügt.<br>
    Wenn nun im Programm das ADODataSet wieder aktiviert wird, kommt es häufig zu Zugriffsverletzungen (z.B. "Lesen von Adresse 000000C3"). Der erste Aufruf funktioniert aber immer. <br>

    Das Problem tritt auf unter Oracle8 und MS-SQL2000, clUseClient und clUseServer.<br>

    (System: NT4.0, SP5 / SP6a / D5 Enterprise inkl. Update und ADOUpdate2)<br>
    <br>

    Hier der abgespeckte Sourcecode:<br>
    <br>

    <PRE>
    ...
    with ADODataSet do
    begin
    Close; // DataSet schliessen
    CommandText := mein_SQL_Statement; // SQL-Statement schreiben
    FieldDefs.Clear; // Felddefinitionen löschen
    Fields.Clear; // Felder löschen
    FieldDefs.Updated := false; // Felddefinitionen sollen ungedated werden
    FieldDefs.Update; // Felddefinitionen holen

    for i := 0 to FieldDefs.Count-1 do
    FieldDefs[i].CreateField(ADODataSet); // Felder erzeugen

    for i := 0 to (Anz_Felder-1) do
    Add_Field(ADODataSet,'Feld'+IntToStr(i),fkCalculat ed,ftString,
    20,taLeftJustify); // berechnete Felder hinzufügen

    try
    open; // DataSet öffnen ==> hier knallt’s
    except
    ...
    end;
    end;
    ...

    //################################################## ####
    // Feld anlegen
    procedure Field_Add(DS:TDataSet; Feldname:String;
    FK:TFieldKind; FT:TFieldType;
    S:Integer;AL:TAlignment);
    var
    Field:TField;
    begin
    if DS.FindField(Feldname) = nil then
    begin
    with TStringField.Create(DS) do
    begin
    FieldName := Feldname;
    DisplayLabel := Feldname;
    FieldKind := FK;
    Dataset := DS;
    Name := Dataset.Name + Feldname;
    Size := S;
    Alignment := AL;
    DS.FieldDefs.Add(Name, FT, Size,False);
    end;
    end;
    end;
    ...
    </PRE>
    <br>
    <br>

    Habe ich etwas Elementares falsch gemacht oder liegt es an den ADO-Komponenten? Muss ich etwa auf _recordset-Ebene runter?<br>
    Ich bin für jede Hilfe dankbar!<br>
    <br>
    Ulrich

  • #2
    Hallo,

    &gt;Der erste Aufruf funktioniert aber immer.

    die ADO Express-Komponeten sind nur ein Aufsatz auf die nativen ADO-Objekte. Das oben beschriebene Verhalten deutet darauf hin, das die nachträgliche TADODataSet-Änderung zu einem unsynchronen Zustand mit den darunterliegenden ADO-Objekten führt.

    &gt;Muss ich etwa auf _recordset-Ebene runter?

    Damit würde ich anfangen, wobei das "lebende" Recordset dann TADODataSet untergeschoben wird. Das folgende Beispiel demonstriert diese Technik - die Daten werden über das Command-Objekt durch den Aufruf einer Stored Procedure eingesammelt und dann an TADODataSet gehängt (<i>ADODataSet2.Recordset := aRS</i>), damit die Daten im TDBGrid dargestellt werden können (TADODataSet hat keine Verbindung zur Datenbank und wird auch sonst <b>nicht</b> konfiguriert). Wenn das Recordset untergeschoben wurde, kann man seine Field-Instanzen anlegen (das habe ich jedoch in dieser Form nicht selbst ausprobiert):
    <pre>
    procedure TForm1.ButtonADOdbGoClick(Sender: TObject);
    var
    aConnection : _Connection;
    aCommand : _Command;
    aParam : _Parameter;
    aRS : _RecordSet;
    vRowsAffected: OleVariant;
    iReturnValue : Integer;
    iError : Integer;
    sError : String;
    iCol,iRow : Integer;
    begin
    aConnection := CoConnection.Create;
    with aConnection do
    begin
    CursorLocation := adUseClient;
    Open(ADOConnection1.ConnectionString,'', '', adConnectUnspecified);
    end;
    try
    aCommand := CoCommand.Create;
    try
    with aCommand do
    begin
    CommandType := adCmdStoredProc;
    Set_CommandText('spSPTestEx');
    Set_ActiveConnection(aConnection);
    // Rückgabewert
    aParam := CreateParameter('RETURN_VALUE', adInteger,
    adParamReturnValue,4, EmptyParam);
    Parameters.Append(aParam);
    // Input-Parameter
    aParam := CreateParameter('@sWert', adVarChar,
    adParamInput, 10, EmptyParam);
    Parameters.Append(aParam);
    Parameters[1].Value := 'Command';
    // Recordset-Objektinstanz für die SP-Datenmenge
    aRS := CoRecordset.Create;
    aRS.Set_ActiveConnection(aConnection);
    aRS.CursorLocation := adUseClient;
    // Command-Objekt ausführen, Ergebnisse abholen
    aRS.Open(aCommand, EmptyParam, adUseClient,
    adLockBatchOptimistic, adOptionUnspecified);
    // Fehler aufgetreten?
    with aConnection do
    if Errors.Count > 0 then
    begin
    for iError := 0 to Errors.Count - 1 do
    sError := sError + Errors[iError].Description + #10#13;
    ShowMessage(sError);
    end;

    aRS.Set_ActiveConnection(nil);

    // natives COM-Objekt an TADODataSet anhängen
    ADODataSet2.Recordset := aRS;
    ADODataSet2.Active := True;

    StatusBar1.SimpleText := IntToStr(vRowsAffected);
    end;
    finally
    aCommand := nil;
    end;
    finally
    aConnection.Close;
    aConnection := nil;
    end;
    end;
    </pre>
    Bevor beim 2. Aufruf erneut auf die Fields-Kollektion von TADODataSet zugegriffen wird (um diese zu leeren), sollte die Recordset-Eigenschaft auf nil gesetzt werden (damit ADO Express zu diesem Zeitpunkt wieder vollständig abgekoppelt wird).

    Es gibt auch Alternativen zu den berechneten Feldern (TFields), da man beliebig viele eigene Spalten dem SELECT-Recordset hinzufügen kann, um diese im eigenen Programm mit Daten zu füllen. An dieser Stelle ist ADO ja deutlich flexibler als die BDE/IBX

    Comment


    • #3
      Hallo Herr Kosch!

      Besten Dank für die schnelle Hilfe. Ich habe meinen Quellcode Ihrem Beispiel entsprechend geändert und bisher sind keine Schutzverletzungen mehr aufgetreten. THX!!! Allerdings habe ich nun das Problem, dass sich keine CalculatedFields mehr hinzufügen lassen (FieldDefs.Clear, Fields.Clear, FieldDefs.Update,Add_Field() etc.), da die Datenmenge schon geöffnet ist. Daher habe ich mein SQL-Statement um Dummy-Felder erweitert ("select ... 'DATE' "Date",'TIME' "Time" from ..."). OnCalcFields sollten diese Felder nun -wie bisher- mit Leben gefüllt werden:

      <pre>
      procedure TDM.ADODataSetCalcFields(DataSet: TDataSet);
      var
      ...
      begin
      DataSet.FieldValues['Date'] := DataSet.FieldValues['DateTime'];
      DataSet.FieldValues['Time'] := DataSet.FieldValues['DateTime'];
      ...
      end;
      </pre>
      (Hinweis: Die Felder waren bisher vom Typ TFieldDate und TFieldTime)

      Das Problem ist nun aber, dass ich keine CalculatedFields mehr habe und somit "OnCalcField" gar nicht mehr aufgerufen wird. Die Folge: Im Grid werden die Texte "DATE" und "TIME" angezeigt. Muss ich jetzt alle Datensätze meiner Datenmenge durchklapern und die Dummy-Felder berechnen, obwohl der Anwender vielleicht nur die ersten 30 wirklich benötigt?

      Gruss
      Ulric

      Comment


      • #4
        So, jetzt rennt's!

        Ich habe das erweitere Statement wieder verworfen, da es mir nicht gelungen ist, die Dummy-Felder auf "Calculated" umzuschalten.
        Das Ergebnis ist eine Mischung aus alter Lösung und _recordset & Co. Allerdings habe ich aCON global definiert, da ich
        adUseServer verwende und die Connection somit erst beim Beenden des Programms oder Ändern der Sicht schliessen darf.

        So funktioniert es jetzt:

        <pre>
        ...
        ADODataSet.Active := false; // DataSet schliessen
        ADODataSet.Connection := nil;
        ADODataSet.RecordSet := nil;

        aCon := CoConnection.Create;
        aCon.CursorLocation := adUseServer;
        aCon.Openmein_ConnectionString, '', '', adConnectUnspecified);
        try
        aCmd := CoCommand.Create;
        try
        with aCmd do
        begin
        CommandType := adCmdText;
        CommandText := mein_SQL_Statement; // SQL-Statement schreiben
        Set_ActiveConnection(aCon);
        end;
        aRS := CoRecordSet.Create;
        aRS.CursorLocation := adUseServer; //Client;
        try
        aRS.Open(aCmd, EmptyParam, adOpenStatic,
        adLockBatchOptimistic, adCmdUnspecified);

        for i := ADODataSet.Fields.Count-1 downto 0 do
        ADODataSet.Fields[i].Free; // bisherige Felder löschen

        ADODataSet.Recordset := aRS; // zuweisen =&gt; ACTIVE=TRUE + FieldDefs sind verfügbar
        ADODataSet.Recordset := nil; // wieder entziehen =&gt; ACTIVE=FALSE

        for i := ADODataSet.Fields.Count-1 downto 0 do
        ADODataSet.Fields[i].Free; // Felder erneut löschen

        for i := 0 to ADODataSet.FieldDefs.Count-1 do
        ADODataSet.FieldDefs[i].CreateField(ADODataSet); // Felder erzeugen

        for i := 0 to (Anz_Felder-1) do
        Add_Field(ADODataSet,'Feld'+IntToStr(i),fkCalculat ed,ftString,
        20,taLeftJustify); // berechnete Felder hinzufügen

        ADODataSet.Recordset := aRS; // zuweisen =&gt; ACTIVE=TRUE
        finally
        end;
        finally
        aCmd := nil;
        end;
        finally
        // aCon.Close; // entfällt, da adUseServer
        end;
        ...
        </pre>

        So, ich wünsche allen eine schönes Wochenende!<br>
        Ulric

        Comment

        Working...
        X