|
Antwort |
Registriert seit: 3. Sep 2004 434 Beiträge Delphi 10.4 Sydney |
#1
Datenbank: MS Access • Version: 2003 • Zugriff über: TAdoConnection
Hallo zusammen!
Ich verwende in meinem Programm TAdoConnection um auf eine Access-Datenbank zuzugreifen (.mdb-Datei).
Zitat:
Bevor ihr mich auffresst: Die Kunden können mittlerweile meine Software auch mit einem SQL-Server benutzen ( https://www.delphipraxis.net/204437-...g-welches.html ), allerdings lässt sich das nicht bei allen Kunden unmittelbar umstellen oder umsetzen, da es dort teilweise nicht gewünscht oder schlicht und einfach überdimensioniert wäre. Dementsprechend muss ich mich jetzt um die Bestandskunden(probleme) kümmern.
Seit Mai 2020 kommt es regelmäßig zu dem Problem, dass die Datenbankdatei beim Schreiben auch nur kleinster Änderungen korrumpiert wird (nachgewiesenes Windows-Update Problem). Nachdem das Verhalten mit den Windows-Updates vom August scheinbar größtenteils behoben wurde, erhalte ich von immer mehr Kunden (aktuell: 3) die Meldung über weitere Datenbankprobleme. Jedoch nicht, dass die Datenbank defekt ist, sondern dass die Software sich merkwürdig verhält. Ein Blick in die Logs offenbart folgendes: TAdoQuery feuert beim Lesen von Werten oder beim Öffnen der Abfrage ("Select Top 1 * from") folgende Exceptions:
Code:
LoadFromDatabase.Exception: Zu viele aktive Benutzer
Code:
Anmerkung: Hier habe ich nicht aus Datenschutzgründen den Inhalt für "Benutzer" und "Computer" entfernt, die Werte sind tatsächlich leer!
GetDB_Int.Exception: Die Datenbank wurde von Benutzer '' auf Computer '' in einen Status versetzt, in dem sie nicht geöffnet oder gesperrt werden kann
Nun, die Fehlermeldungen an sich sind ja verständlich. Warum sie erscheinen, entbehrt jedoch jeglicher Grundlage. 1) Die Installation wurde seit einem Jahr nicht verändert. Die Benutzer arbeiten wegen den vorherigen Störungen aktuell "eher weniger" mit der Software, als mehr. Mit "zu viele Benutzer" meint das Programm ca... 3 Stück insgesamt? Die Zahl ist nicht höher als früher auch. Warum kommt es jetzt zu dem Problem? 2) "In einen Status versetzt" - ich nehme an, das soll heißen, sie ist Exklusiv geöffnet. Mein Programm macht das an keiner Stelle (absichtlich). Ich würde hier fast vermuten, dass z.B. Virenscanner oder Backupprogramm die Datei nach einem Schreibvorgang vor (weiteren) Änderungen schützen, bis sie komplett gesichert / gescannt wurde. Google-Suchen offenbaren viele Threads (größtenteils von vor vielen Jahren) zu dem Thema, meist auch mit den Aussagen, dass das Problem "irgendwann" auftaucht und nicht mehr weggeht. Dann wurden z.B. Backups wiederherstellt, die selben Daten eingetragen, dann trat das Problem nicht mehr auf. Das würde der Windows-Update- oder Virescanner-Theorie wiedersprechen. Die Datenbank bei den Kunden wurde schon in Access "komprimiert und repariert", ein veraltetes Backup von vor > 2 Wochen würde ich ungern einspielen müssen "nur um zu testen"... Ist das was zu dem Thema bekannt? Generell bin ich nun sehr verunsichert, was das Thema "Exklusiver Zugriff" angeht. Generell will ich keinerlei geschützten Zugriff auf die Daten in der Datenbank. Verbindungsaufbau:
Delphi-Quellcode:
Aus der Recherche und Forensuche habe ich herausgefunden, dass sich die TAdoConnection unter bestimmten Bedingungen selbständig in den Exklusiven Modus schaltet:
db.Close;
db.KeepConnection := HAL_Registry_GetBool('KeepConnection', True); db.LoginPrompt := False; db.ConnectionString := 'Provider=Microsoft.Jet.OLEDB.4.0;' + 'Data Source=' + _FileName + ';' + 'Mode=Share Deny None;' + 'Persist Security Info=False;'; db.Open; 1) Änderung an der Datenbankstruktur 2) ??? In der Tat ist es richtig, dass mein Programm auch die Datenbank nach einem Update auf das aktuelle Patchlevel anhebt (hinzufügen von Spalten oder sogar ganzen Tabellen) - laut Forum springt hier die AdoConnection in den exklusiven Modus. Dies geschieht jedoch nur einmalig nach dem Update, nicht bei jedem Programmstart. Und danach wird die TAdoConnection eh getrennt und neu verbunden - das soll angeblich helfen, damit Share Deny None wieder richtig greift. -Sind andere Situationen bekannt, in denen ich mit ADO versehentlich eine exklusive Sperre auf eine Tabelle legen könnte? -Spielt die CursorLocation eine Rolle? Diese wird von mit aktuell nicht explizit gesetzt und verwendet den Standardwert... -Wird die Sperre sicher damit aufgehoben, dass ich einfach nur db.close; db.open; mache, oder muss ich nochmal explizit den ConnectionString neu zuweisen oder eine andere Eigenschaft der TAdoConnection ändern? -Wonach kann ich noch schauen? (M)eine aktuelle Empfehlung an die Kunden lautet, meine datenbank.mdb und datenbank.ldb von der Echtzeitprüfung im Virenscanner auszunehmen und die Umstellung auf SQL priorisiert in Betracht zu ziehen. Sonst noch eine Idee?
Delphi 10.4 32-Bit auf Windows 10 Pro 64-Bit, ehem. Delphi 2010 32-Bit auf Windows 10 Pro 64-Bit
|
Zitat |
Registriert seit: 3. Sep 2004 434 Beiträge Delphi 10.4 Sydney |
#2
Ich bitte vielmals um Entschuldigung für diesen Bump, aber es gibt wichtige Erkenntnisse:
Das hier ist mein Befehl zum Aufbau der Datenbankverbindung:
Delphi-Quellcode:
Vor db.open ist
db.Close;
db.KeepConnection := HAL_Registry_GetBool('KeepConnection', True); db.LoginPrompt := False; db.ConnectionString := 'Provider=Microsoft.Jet.OLEDB.4.0;' + 'Data Source=' + _FileName + ';' + 'Mode=Share Deny None;' + 'Persist Security Info=False;'; db.Open;
Code:
NACH db.open ist
db.ConnectionString=Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\test\db.mdb;Mode=Share Deny None;Persist Security Info=False;
Code:
d.h. Delphi, Ado (oder wer auch immer) schreibt mir die ganzen Attribute in den ConnectionString rein, bzw. listet alle Eigenschaften auf, die über diese Api für diese Datenbankverbindung verfügbar sind. Diese Attribute kann man ja regulär z.B. über
db.ConnectionString=Provider=Microsoft.Jet.OLEDB.4.0;User ID=Admin;Data Source=C:\test\db.mdb;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;
db.Properties.Item['Mode'].Value setzen oder auslesen. Kann es nun sein, dass eines dieser Attribute für den exklusiven Zugriff sorgt? Jet OLEDB:Database Locking Mode=1 habe ich hier gefunden: https://docs.microsoft.com/de-de/sql...l-server-ver15 , es wird wohl der Datentyp DBPROP_JETOLEDB_DATABASELOCKMODE verwendet:
Zitat:
Jet OLEDB:Database Locking Mode (DBPROP_JETOLEDB_DATABASELOCKMODE) Indicates the locking mode for this database. The first user to open the database determines the mode used while the database is open.
Zitat:
Type: VT_I4
Typical R/W: R/W Description: Jet OLEDB:Database Locking Mode Scheme to use when locking the database. Constants: DBPROPVAL_DL_OLDMODE ? Old mode used in previous releases of Jet's storage engine DBPROPVAL_DL_ALCATRAZ ? Alcatraz mode. Enables locking mode that allows for row-level locking. This does not preclude page locking. A database can be open in only one mode at a time. The first user to open the database determines the locking mode used while the database is open. Eine Google-Suche bringt mich hier hin:
Zitat:
DBPROPVAL_DL_OLDMODE = 0 - Mode used in previous versions of the Jet database
DBPROPVAL_DL_ALCATRAZ = 1 - Mode used in Jet 4 and later, allowing row level locking Gibt es noch andere Parameter für den Verbindungsaufbau, den ich berücksichtigen oder ändern muss, damit die Datenbank nicht exklusiv geöffnet ist? Gerade eben hatte ich auch (lokal, mit nur einem PC, aber dafür 2 offenen TAdoConnection innerhalb der .exe) diese Meldung:
Zitat:
Datei konnte nicht gesperrt werden / SELECT TOP 1 * FROM T_Integer WHERE ID=:pID
Edit: -Wie kann ich prüfen, ob sich die aktuelle TAdoConnection in einem Exklusiven Modus befindet? -Gibt es für TAdoQuery eine Einstellung, die einen exklusiven Zugriff bewirken?
Delphi 10.4 32-Bit auf Windows 10 Pro 64-Bit, ehem. Delphi 2010 32-Bit auf Windows 10 Pro 64-Bit
Geändert von berens ( 7. Okt 2020 um 17:54 Uhr) |
Zitat |
Registriert seit: 27. Nov 2017 2.490 Beiträge Delphi 7 Professional |
#3
Könnte es sein, dass eines der in Deinem Connctionstring fehlenden Attribut die Fehlerursache ist?
Dein Connectionstring ist jedenfalls unvollständig und wird daher beim Verbindungaufbau mit Defaultwerten"bestückt". Das kann funktionieren, muss es aber nicht und schon garnicht dauerhaft. Bitte bau mit dem Objektinspektor (o. ä.) einen Connectionstring auf und setzt diesen dann in Deinem Programm ein. Welchen Wert hat z. B. OLEDB: Database Locking Mode, wenn es im Connectionstring nicht angegeben wurde? 0 oder 1. Damit hast Du dann ggfls. schonmal das erste Problem. 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. Wenn das Problem dann weiterbesteht, müssen wir mal schauen, ob es auch eine "externe" Ursache für das Problem geben könnte. Momentan bin ich mir nicht sicher, ob die von Dir gewählte Variante dauerhaft stabil funktionieren kann und dauerhaft stabil funktionieren muss. Jedenfalls hab' ich, seit dem ich mir angewöhnt habe, ConnectionStrings grundsätzlich vollständig zu erstellen, keine Probleme mehr bei dem Zugriff auf Datenbanken über ADO gehabt. |
Zitat |
Registriert seit: 29. Nov 2010 3.072 Beiträge Delphi 2010 Enterprise |
#4
Die Exclusivsperre und die Meldung "zu viele Nutzer" könnte auch die Folge von fehlerhaften oder falsch bewerteten Zugriffsversuchen sein.
Muster: Jeder Zugriff der 3 Nutzer wird (auf Dateiebene) nicht sauber beendet, jeder weitere Zugriff ist dann ggF. eine neue Verbindung. 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. Falls es immer mehr werden, stimmt ja die Meldung "zu viele Nutzer".
Gruß, Jo
|
Zitat |
Registriert seit: 3. Sep 2004 434 Beiträge Delphi 10.4 Sydney |
#5
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.
Delphi-Quellcode:
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.
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; 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
-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:
-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.
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); -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:
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.
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; -Wie setzt man sowas korrekt um? Wie habt Ihr das gelöst? -Ist
Delphi-Quellcode:
genau richtig, falsch, übertrieben, ...?
finally
try q.close; finally FreeAndNil(q); end; end; 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
|
Zitat |
Registriert seit: 27. Nov 2017 2.490 Beiträge Delphi 7 Professional |
#6
Hilfe, soviele Fragen und so richtig kann ich das alles nicht beantworten
Hier erstmal ein Stück Code aus 'nem Programm, das Ado nutzt und auf diverse Datenbanken zugreifen können sollen muss
Delphi-Quellcode:
Abhängig vom Datenbanktyp unterscheiden sich die Werte für CursorLocation und CursorType. Ohne diese Unterscheidung gibt es garantiert Fehler (zumindest auf meinen Systemen).
// Hier den Datenbanktypen abfragen und die Werte für Cursor ... setzen.
// CursorLocation bei SQLite muss wohl clUseClient sein, // bei Access aber clUseServer // Hier noch testen, was bei SQLite am sinnvollsten ist. if fDBIsSQLite then begin // ctUnspecified, ctOpenForwardOnly, ctKeyset, ctDynamic, ctStatic // Diese Variante spart Arbeitsspeicher und ist schnell. // Aber es gibt keine RecNo. // Änderungen von Datensätzen nicht möglich. // con.CursorLocation := clUseServer; // Dann gibt es keine RecNo :-( // qrySQL.CursorType := ctKeyset; // qrySQL.CursorType := ctStatic; // Benötigt viel Arbeitsspeicher, deutlich mehr, als die Datenbankgröße. // Ist sehr langsam. qrySQL.CursorLocation := clUseClient; qrySQL.CursorType := ctDynamic; // qrySQL.CursorType := ctStatic; // qrySQL.CursorType := ctKeyset; dbckOK.ValueChecked := 'T'; dbckOK.ValueUnchecked := 'F'; end else if fDBIsAccess then begin qrySQL.CursorLocation := clUseServer; qrySQL.CursorType := ctStatic; dbckOK.ValueChecked := 'Wahr'; dbckOK.ValueUnchecked := 'Falsch'; end else if fDBIsFirebird then begin qrySQL.CursorLocation := clUseClient; qrySQL.CursorType := ctDynamic; dbckOK.ValueChecked := '1'; dbckOK.ValueUnchecked := '0'; end else begin // Ansonsten? qrySQL.CursorLocation := clUseClient; qrySQL.CursorType := ctDynamic; dbckOK.ValueChecked := '1'; dbckOK.ValueUnchecked := '0'; end; Da das Ganze schon ein paar Jahre alt ist, weiß ich nicht mehr so genau, wie ich zu welchen Werten kam, aber seit dem scheint es rund zu laufen. Mein Vorgehen wäre von daher zuerst mal mit CursorLocation und CursorType zu experimentieren, bis Dein Testprogramm, das Du zweimal gestartet hast, keinen Fehler mehr wirft oder Du garantiert alle Kombinationen der beiden Eigenschaften "durchhast" und weißt, dass sie alle zu Fehlern führen. Mit dieser Funktion baue ich mir aus meinen Programmen heraus den Connectionstring auf (sofern wahlweise auf unterschiedliche Datenbanken zugegriffen werden soll).
Delphi-Quellcode:
Was kommt bei Deinem Testprogramm dabei raus? Stimmt das mit dem von Dir selbst erstellten Connectionstring überein oder mit dem vom Assistenten generierten? Bei meinen System stimmt es eher mit Deinem (im vorherigen Post) selbst erstellten Connectionstring überein.
function fnGetDataSourceString(Handle : THandle;
ADOConnection : TADOConnection; sConnection : String = '') : Boolean; begin ADOConnection.Connected := False; ADOConnection.ConnectionString := PromptDataSource(handle,sConnection); ADOConnection.Connected := True; Result := ADOConnection.Connected; end; Hier ein Beispiel:
Code:
Querys werden bei mir nach Gebrauch sofort per Close geschlossen. Allerdings erstelle ich sie nicht dynamisch, sondern hab' sie gewöhnlich auf 'nem TDataModule, auch wenn es da durchaus mal eine ganze Reihe für unterschiedliche Zwecke geben kann.
Provider=Microsoft.Jet.OLEDB.4.0;User ID=Admin;Data Source=C:\Support\TipVerwaltung.mdb;Mode=Share Deny None;Extended Properties="";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
Zur Reihenfolge: Weise bitte zuerst den Connectionstring zu und ändere erst dann (soweit erforderlich) die Eigenschaften der ADOConnection. Ich gehe mal davon aus, dass die von Dir gesetzten Werte beim Setzen des Connetionstring "überschrieben" werden. Machst Du die Änderungen nach der Zuweisung des Connectionstrings, werden "seine" Werte durch Deine überschrieben. Damit wärst Du dann quasi der Gewinner. So, wie Du es momentan zu machen scheinst, gewinnt jedoch der Connectionstring. Als erste Idee (ohne Garantie für irgendwas):
Delphi-Quellcode:
Damit scheinen bei mir mehrere Instanzen eines Programmes problemlos mit einer Accessdatenbank arbeiten zu können.
db.Close;
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;'; db.KeepConnection := True; // HAL_Registry_GetBool('KeepConnection', True); db.LoginPrompt := False; db.CursorLocation := clUseServer; db.CursorType := ctStatic; db.IsolationLevel := ilReadCommitted; db.Mode := cmReadWrite; c1 := db.ConnectionString; db.Open; c2 := db.ConnectionString; |
Zitat |
Registriert seit: 3. Sep 2004 434 Beiträge Delphi 10.4 Sydney |
#7
Vielen Dank für die detaillierte Antwort.
Ja, bei mir kommt auch der lange ConnectionString raus, in sofern verwende ich den jetzt auch. Ich habe all meine Quelltextdatei überarbeitet: -Es wird nun häufiger eine über Parameter übergebene TAdoConnection verwendet, anstatt selbst (temporär) eine neue zu Erstellen -Überall wo eine temporäre AdoConnection erstellt wird, gebe ich paranoia-mäßig alles mit try..finally frei
Delphi-Quellcode:
Was mich mit deinem Post jetzt aufgeschreckt hat:
finally
CloseQueryAndClearSQLText(q); end; finally db.Close; end; finally FreeAndNil(q); FreeAndNil(db); end; Bisher dachte ich, dass cmReadWrite letztendlich doch "Einzel-Benutzung" bedeutet, da das Wort "Share" fehlt. Ich verwende bis dato (imo problemlos?) immer cmShareDenyNone - "Alle Teilen, nichts verbieten". So wurde das glaube ich auch überall empfohlen und in den Beispielen verwendet. Ich bin eben fast vom Hocker gefallen, als ich in der OH nachgeschlagen habe:
Code:
cmShareDeny... : Der Benutzer kann alles, ausser Read-Lesen, Write-Schreiben, None-Nichts --> Der Benutzer kann alles, mit Ausnahme von nichts - ohne Ausnahme, siehe auch: https://www.delphipraxis.net/107684-post4.html
cmReadWrite - Die Verbindung verfügt über Lese-/Schreibrechte.
cmShareDenyRead - Die Verbindung kann von anderen Benutzern nicht mit Leseberechtigung geöffnet werden. cmShareDenyWrite - Die Verbindung kann von anderen Benutzern nicht mit Schreibberechtigung geöffnet werden. cmShareDenyNone - Die Verbindung kann mit keiner Art von Zugriffsrecht von anderen Benutzern geöffnet werden.
Zitat:
nein, MSAccess ist auch für einen Mehrbenutzermodus vorgesehen
der Verbindungsstring wird normalerweise mit den Zugangsberechtigungen "share deny none' also jeder kann gleichzeitig zugreifen s.a. Connection.Mode cmShareDenyNone Hier bräuchte ich jetzt bitte echt schnell nochmal Rückmeldung: Was Unterscheidet cmShareDenyNone von cmReadWrite? Ist cmReadWrite wirklich für Mehrbenutzer-Zugriff für Access-Datenbanken über Netzwerk geeignet? Können dabei (z.B. nach einem Update) [einmalig] Tabellen angepasst werden (Spalten hinzufügen, ...), oder schlagen irgendwelche Befehle fehl?
Delphi 10.4 32-Bit auf Windows 10 Pro 64-Bit, ehem. Delphi 2010 32-Bit auf Windows 10 Pro 64-Bit
Geändert von berens ( 8. Okt 2020 um 12:19 Uhr) |
Zitat |
Registriert seit: 27. Nov 2017 2.490 Beiträge Delphi 7 Professional |
#8
Ehrlich gesagt: Keine Ahnung.
Habe seinerzeit rumprobiert, bis ich zu dem Schluss kam, dass die oben von mir gepostete Variante (für meine Begriffe) ordentlich funktioniert. Die Formulierung
Code:
klingt für mich so: Die Anderen dürfen nix.
cmShareDenyNone - Die Verbindung kann mit keiner Art von Zugriffsrecht von anderen Benutzern geöffnet werden.
Aber: Man kann die Formulierung durchaus auch anders interpretieren. Für mich klingt das so: Egal mit welchen Rechten es die anderen versuchen, sie werden scheitern. Aber man kann es auch so interpretieren: Egal welche Rechte die Anderen nutzen, sie können zugreifen. Bei dieser Art von Hilfe bin ich immer hilflos, denn die Aussage entspricht in etwa einem "Entschiedenen sowoh als auch" (oder so ähnlich). Viele AdoConnection = viele Handles, viele Querys = viele Handles. Was ich nicht weiß, inwieweit die Handles beim Freigeben der AdoConnections bzw. der Querys sofort "verschwinden". Von daher ist mein Vorgehen immer so: Alles, was ich brauche, wird bereits in der Entwicklungsumgebung auf's Formular ... gepappt. Dabei gilt: Von allem so wenig wie möglich. Mehrere AdoConnetions nur, wenn auch mehrere Datenbanken genutzt werden. Pro DB nur eine Connection. Querys: Eine für Abfragen, eine für Insert, Update, Delete ... (also den Rest) (ggfls. pro DB). Pro DBGrid, DBCtrlGrid, DataSource (o. ä.) (sofern welche zum Einsatz kommen) eine Query. Connections verbinden sich beim Programmstart mit der Datenbank und beenden die Verbindung beim Programmende. Auch hier gilt: So wenig wie möglich. Ob dieses Vorgehen nun den Regeln der Kunst entspricht, mit irgendwelchen Vorgehensmodellen, ... konform geht, keine Ahnung. Fahre mit diesem Vorgehen seit fast 2 Jahrzehnten gut und solange läuft auch schon ein Teil meiner Software ohne Mucken. Geändert von Delphi.Narium ( 8. Okt 2020 um 12:42 Uhr) Grund: Schreibfehler |
Zitat |
Registriert seit: 9. Apr 2006 1.682 Beiträge Delphi 5 Professional |
#9
Vielleicht lohnt sich ein Blick auf die Englische Erklärung:
Sorry, wenn der Beitrag nicht (sonderlich) hilfreich gewesen sein sollte. Grüße Dalai |
||||||||||||||||||
Zitat |
Ansicht |
Linear-Darstellung |
Zur Hybrid-Darstellung wechseln |
Zur Baum-Darstellung wechseln |
ForumregelnEs ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.
BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus. Trackbacks are an
Pingbacks are an
Refbacks are aus
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
Gehe zu... |
LinkBack |
LinkBack URL |
About LinkBacks |