Einzelnen Beitrag anzeigen

berens

Registriert seit: 3. Sep 2004
434 Beiträge
 
Delphi 10.4 Sydney
 
#5

AW: Access-Datenbank gesperrt - Problem mit Windows-Update?

  Alt 7. Okt 2020, 21:10
Schon mal danke für die Rückmeldungen!

Zitat:
Es ist immer sinnvoll, wenn alle Programme, die auf eine Datenbank zugreifen, den gleichen und vollständigen Connectionstring nutzen und nicht einen, der mal so gerade eben (hoffentlich) den Minimalanforderungen gerecht wird.
Meine Herangehensweise war bei der aktuellen Umstellung: Auf Kompatibilität achten, nur die Werte explizit festlegen, die auch benötigt werden. Deine Argumentation im Sinne "Nichts dem Zufall überlassen" kann ich natürlich aus dieser Sichtweise heraus gut nachvollziehen. Ich habe mit einer TAdoConnection auf einem neuen Form beispielhaft eine Verbindung zu einer Access-Datenbank eingerichtet. Der vom Assistenten generierte String war fast identisch mit meinem. Erst beim Aufbau der Verbindung wurde der ConnectionString dieser Komponente so erweitert, das er letztendlich identisch ist als der im vorherigen Post genannte nach "db.open". Auf deinen Rat hin habe ich nun also meine Verbindungsaufbau-Prozedur (die von allen Programmteilen verwendet wird, sobald eine Verbindung zur einer Access-Datenbank hergestellt werden muss) angepasst:

Delphi-Quellcode:
    db.Close;
    db.Mode := cmShareDenyNone;
    db.KeepConnection := HAL_Registry_GetBool('KeepConnection', True);
    db.LoginPrompt := False;
    db.ConnectionString := 'Provider=Microsoft.Jet.OLEDB.4.0;'
                       + 'User ID=Admin;'
                       + 'Data Source=' + _FileName + ';'
                       + 'Mode=Share Deny None;'
                       + 'Jet OLEDB:System database="";'
                       + 'Jet OLEDB:Registry Path="";'
                       + 'Jet OLEDB:Database Password="";'
                       + 'Jet OLEDB:Engine Type=5;'
                       + 'Jet OLEDB:Database Locking Mode=1;'
                       + 'Jet OLEDB:Global Partial Bulk Ops=2;'
                       + 'Jet OLEDB:Global Bulk Transactions=1;'
                       + 'Jet OLEDB:New Database Password="";'
                       + 'Jet OLEDB:Create System Database=False;'
                       + 'Jet OLEDB:Encrypt Database=False;'
                       + 'Jet OLEDB:Don''t Copy Locale on Compact=False;'
                       + 'Jet OLEDB:Compact Without Replica Repair=False;'
                       + 'Jet OLEDB:SFP=False;'
                       + 'Persist Security Info=False;';

// if db.CursorLocation <> clUseServer then begin
// db.CursorLocation := clUseServer;
// end;

    c1 := db.ConnectionString;
    db.Open;
    c2 := db.ConnectionString;
Beim Betrachten der TAdoConnection in diesem neuen Form ist mir wieder in den Sinn gekommen, dass ja viele Attribute direkt am Delphi-Objekt geändert werden können (z.b. db.Mode := cmShareDenyNone; ). Ob dies jetzt Vorrang vor dem 'Mode=Share Deny None;' aus dem ConnectionString hat, sich evtl. sogar wiederspricht oder ignoriert wird: Ich habe es vorsichtshalber nochmal explizit reingeschrieben, im notfall gilt hier: "Doppelt hält besser", da die Werte ja an beiden Stellen identisch sind.

Die Idee mit dem "Minimalismus" war und ist auch die Angst vor Exceptions beim Verbindungsaufbau. Falls mein Programm jetzt unter einer anderen Windows-Version eingesetzt wird, und ich ein Attribut mitgebe, was dort vielleicht nicht bekannt ist oder unterstützt wird: Jetzt geht hier gar nichts mehr (Verbindungsaufbau aller Datenbanken schlägt fehl), weil ein -von mir eh nicht "gewolltes"- übergebens Attribut nicht bekannt ist. Wahrscheinlich passiert gar nichts, und die Sorge ist unbegründet. Z.B. verwende ich seit Jahren 'Persist Security Info=False;' - der Assistent baut das Attribut auch beim Verbindungsaufbau nicht mit ein - zu stören scheint es aber auch nicht.

Was mir hier noch Sorgen macht, ist db.CursorLocation. Ich habe in der OnlineHilfe und im Internet schon einiges drüber gelesen, weiß aber nicht, "ob" und in "wie weit" das für mich relevant ist. Nehmen wir mal an, alle Abfragen bei allen Tabellen und Kunden liefern i.d.R. nicht mehr als 20 Datensätze zurück. Hat CursorLocation jetzt eine Auswirkung auf irgendwelche Datensatz- oder Tabellensperren? Auf die Geschwindigkeit? In der OH steht u.a. "Viele Server unterstützen außerdem nur Vorwärts-Cursor, bei denen der Satzzeiger in der Ergebnismenge nicht in Richtung Tabellenanfang bewegt werden kann." --> Unerwartetes Verhalten / Probleme beim Ausführen einfacher Operationen wie AdoQuery.First etc.? Hört sich gefährlich an... Generell würde ich den Wert ja "unverändert" auf clUseClient lassen.
-Spricht was dagegen?
-TAdoQuery hat auch die Eigenschaft "CursorLocation". Hier ebenfalls clUseClient?


Zitat:
Ich würde mal schauen, ob ich auf Seite des Dateiservers, der die Access Datei und die Lockdatei hostet, einen genauen Überblick über die (offenen) Dateihandles bekommen kann
Da gebe ich dir vollkommen recht. Ich denke, die fehlgeschlagenen Abfragen haben die Datenbankverbindung nicht ordentlich wieder abgebaut.

-Erzeugt nur jede neue TAdoConnection.Open ein zusätzliches Handle auf die .mdb-Datei oder auch schon z.B. jedes TAdoQuery mit .Open?

Bei der großen Umstellung (Link siehe Oben) wurde auch flächig der Zugriff der AdoQuerys überarbeitet. In der Tat habe ich schon bei vielen Kunden auf den FileServern gesehen, das es *schäm* sehr, sehr viele offene Handles auf die Datenbankdatei gegeben hat. Aus anderen Beiträgen hier im Forum habe ich entnommen, dass es nicht langt, ein AdoQuery einfach nur freizugeben (mit FreeAndNil), nein es MUSS explizit mit "Close" geschlossen werden. Bestätigung, Ja, Nein, andere Meinung?

Beispielhaft sieht mein Zugriff auf die Datenbank immer so oder ähnlich aus:

Delphi-Quellcode:
function TRecurrencePattern.SaveToDatabase(db: TAdoQuery): Boolean;
var
  i: integer;
  q: TADOQuery;
begin
  Result := False;

  q := NIL;
  try
    if not assigned(db) then begin
      Log('TRecurrencePattern.SaveToDatabase: db unassigned.');
      Exit;
    end;

    q := TADOQuery.Create(Self);
    q.Connection := db;
    CloseQueryAndClearSQLText(q);

    // diese vier Zeilen sind in eigenen Prozeduren mit dortiger Fehlerbehandlung, nur zum Verständnis hier direkt
    q.SQL.Add('SELECT * FROM T_SerienMuster WHERE refTermin=:ID');
    q.Parameters.ParamByName('ID').Value := _refTermin;
    q.Open;
    q.First;


      if q.Eof then q.Insert else q.Edit;

      q.FieldByName('refTermin').AsInteger := TerminID;
      q.FieldByName('blRecurrsJAN').AsBoolean := blRecurrsJanuar;
      q.FieldByName('blRecurrsFEB').AsBoolean := blRecurrsFebruar;
      q.FieldByName('blRecurrsMAR').AsBoolean := blRecurrsMaerz;
      q.FieldByName('blRecurrsAPR').AsBoolean := blRecurrsApril;
      q.FieldByName('blRecurrsMAY').AsBoolean := blRecurrsMai;
      // usw.
      q.Post;

    Result := True;
  except
    on E: Exception do begin
      Log('TRecurrencePattern.SaveToDatabase', M, E.Message);
    end;
  end;
  FreeAndNil(q);
-try..finally wäre natürlich innerhalb von try..except feiner, und so würde ich das heute auch machen. Aber so muss q auch ordentlich freigegeben werden: q wird ja am Anfang mit NIL initialisiert, und im schlimmsten Fall wird FreeAndNil(Nil) aufgerufen - es passiert nichts schlimmes. Im Falle einer Exception wird nun das wirklich erzeute q ordnungsgemäß freigegeben, da das Programm nach except..end weiterläuft, und diese Zeile somit in jedem Fall aufgerufen wird. Daran sollte es -wenn auch "ungewöhnlich" im vergleich zu einem eingebetteten try..finally- also nicht liegen, dass unnötige FileHandles auf dem Server offen bleiben.

-Diese Prozedur ist wieder ein Paradebeispiel für (meine) schlampige Arbeit: nach dem q.Post fehlt das q.Close. Meine frühere Erwartung an TAdoQuery waren schlicht und ergreifend: Wenn ich es freigebe, wird es schon selbst für eine ordentliche Beendigung sorgen (.Close), und nicht einfach "das Messer in der Sau stecken lassen". Wenn ich das richtig verstehe, ist/könnte aber genau das sein, und ich muss dem TAdoQuery noch explizit hinterherräumen. Wenn man's weiß ist ja gut - wenn man mit einer falschen Erwartung dran geht... naja - woher soll man's wissen. Auch hier spielt wieder die "Angst" vor einer Exception eine große Rolle. Wenn ich das Programm jetzt wie folgt um schreiben würde:
Delphi-Quellcode:
function TRecurrencePattern.SaveToDatabase(db: TAdoQuery): Boolean;
var
  i: integer;
  q: TADOQuery;
begin
  Result := False;

  q := NIL;
  try
    try
      if not assigned(db) then begin
        Log('TRecurrencePattern.SaveToDatabase: db unassigned.');
        Exit;
      end;

      q := TADOQuery.Create(Self);
      q.Connection := db;
      CloseQueryAndClearSQLText(q);

      // diese vier Zeilen sind in eigenen Prozeduren mit dortiger Fehlerbehandlung, nur zum Verständnis hier direkt
      q.SQL.Add('SELECT * FROM T_SerienMuster WHERE refTermin=:ID');
      q.Parameters.ParamByName('ID').Value := _refTermin;
      q.Open;
      q.First;

      if q.Eof then q.Insert else q.Edit;

      q.FieldByName('refTermin').AsInteger := TerminID;
      q.FieldByName('blRecurrsJAN').AsBoolean := blRecurrsJanuar;
      q.FieldByName('blRecurrsFEB').AsBoolean := blRecurrsFebruar;
      q.FieldByName('blRecurrsMAR').AsBoolean := blRecurrsMaerz;
      q.FieldByName('blRecurrsAPR').AsBoolean := blRecurrsApril;
      q.FieldByName('blRecurrsMAY').AsBoolean := blRecurrsMai;
      // usw.
      q.Post;

      Result := True;
    finally
      q.Close;
      FreeAndNil(q);
    end;
  except
    on E: Exception do begin
      Log('TRecurrencePattern.SaveToDatabase', M, E.Message);
    end;
  end;
Für FreeAndNil sollte es zunächst keinen Unterschied machen. Wenn es aber nun -aus welchen Gründen auch immer- vorkommen *könnte* dass die Erzeugung von q fehlschlägt, bzw. dieses im Laufe der Prozedur auf einen ungültigen Arbeitsspeicherbereich zeigt (jaja, Paranoia etc., aber spielen wir das Spiel mal zu Ende, bitte!) bzw. eine Exception zwischendrin ausgelöst wird, und finally nun angesteuert wird: q.Close würde/könnte hier eine *zusätzliche* Exception schmeißen (weil q ja ungültig oder der Close-Befehl "einfach so" eine Exception wirft), und dann wird FreeAndNil(q) ja gar nicht aufgerufen --> wirkliches MemoryLeak.
-Wie setzt man sowas korrekt um? Wie habt Ihr das gelöst?
-Ist
Delphi-Quellcode:
  finally
    try
      q.close;
    finally
      FreeAndNil(q);
    end;
  end;
genau richtig, falsch, übertrieben, ...?

Danke im vorraus!
Delphi 10.4 32-Bit auf Windows 10 Pro 64-Bit, ehem. Delphi 2010 32-Bit auf Windows 10 Pro 64-Bit
  Mit Zitat antworten Zitat