Datenbank Anwendung im Netzwerk

  • VB.NET

Es gibt 30 Antworten in diesem Thema. Der letzte Beitrag () ist von GerhardW.

    Datenbank Anwendung im Netzwerk

    Guten Morgen,

    meine Anwendungen sind meist GUI Anwendungen, wo Daten von einer Datenbank angezeigt und verändert werden können. Also nicht wirklich was kompliziertes.
    Nun wollte ich euch fragen, wie ihr so eine Anwendung im Netzwerk macht, wenn mehrere Benutzer auf die gleichen Datenbanken zugreifen. Speziell interessiert
    mich das "Record Locking". Ich muss ja im Netzwerk den geladenen Datensatz für die anderen Benutzer sperren, damit keine Datenkonflikte auftreten. D.h. ein Benutzer die Daten des anderen wieder überschreibt.

    Als Datenbank haben meine Kunden MySQL und MS SQL im Einsatz. Soweit ich mich eingelesen habe, gibt es da in den .NET Treiberlibraries keine Funktionen um einen geladenen Datensatz zu sperren und wieder freizugeben.

    Ich verwende dazu zwei Ansätze:
    1) Beim Laden wird ein Flag gesetzt = Spalte in der Tabelle, welche gesetzt wird und beim Verlassen des Datensatzes wieder gelöscht
    2) Ich schaue, ob vor dem Speichern des Datensatzes dieser sich geändert hat. Wenn geändert, dann Meldung an den Benutzer

    Wie löst ihr solche Situationen? Ich danke für euere Infos.
    Ich hab mich zum Glück immer um dieses Problem herum-laviert.
    Dennoch denke ich, einen Ansatz vorschlagen zu können, eine Variante des "Pessimistic Lock":
    Im Gui ist zunächstmal nix editierbar - nur angugge.
    Will User editieren, muss die Anwendung quasi in einen Editier-Modus gehen - Anbieten täte sich, wenn ein modales Form öffnet, und den zu editierenden Datensatz präsentiert.
    Gleichzeitig wird versucht, einen Timestamp in den Datensatz zu schreiben - das ist der Lock. "Lock holen" darf nur gehen, wenn der vorherige Timestamp abgelaufen ist.
    Der Timestamp sagt aus, wann der Lock verfällt - falls der User seine Eingabe nie beendet.
    Beendet der User seine Eingabe wird der Timestamp rückgesetzt, auf Date.MinValue.

    Alles weitere und drumherum kannste dir vermutlich denken.
    Gleichzeitig wird versucht, einen Timestamp in den Datensatz zu schreiben - das ist der Lock. "Lock holen" darf nur gehen, wenn der vorherige Timestamp abgelaufen ist.
    Der Timesstamp ist ja eine momentane Zeit. Was meinst du mit "abgelaufen", wenn der Wert wieder auf Date.MinValue gesetzt wird?

    Der Timestamp sagt aus, wann der Lock verfällt - falls der User seine Eingabe nie beendet.
    Wie kann ich den Lock verfallen lassen ohne den Wert zu ändern?

    Denke ich da zu kompliziert oder vestehe ich deinen Ansatz nicht?

    GerhardW schrieb:

    Als Datenbank haben meine Kunden MySQL und MS SQL im Einsatz

    Hallo,
    generell kümmern sich beide Server-Technologien selbst um das Locking docs.microsoft.com/de-de/sql/r…and-row-versioning-basics dev.mysql.com/doc/refman/8.0/en/innodb-locking.html. Deine Anwendung muss "nur" einen Timeout/Deadlock abfangen und entsprechend reagieren.
    Und? Warum sollte man auch bei einem Mehrbenutzersystem (das bei Kunden zum Einsatz kommt) eine Datenbankengine (MyISAM in diesem Fall) verwenden, die keine Transaktionen und keine referenzielle Integrität unterstützt verwenden? Das ergibt keinen Sinn. Datensicherheit geht bei Kunden vor.

    GerhardW schrieb:

    Gleichzeitig wird versucht, einen Timestamp in den Datensatz zu schreiben - das ist der Lock. "Lock holen" darf nur gehen, wenn der vorherige Timestamp abgelaufen ist.
    Der Timesstamp ist ja eine momentane Zeit. Was meinst du mit "abgelaufen", wenn der Wert wieder auf Date.MinValue gesetzt wird?
    Ja, "Timestamp" ist nicht das richtige Wort. Ich meine "Ablaufzeit".
    Also wenn ich um 9:00 locke, schreibt der mw als Timestamp/Ablaufzeit 11:00 rein.
    Ab 11:00 ist der Lock also abgelaufen, und andere User dürfen sich den Lock holen. Ob ich selbst da dann doch noch abspeichern kann, nach 2h, wenn zwischenzeitlich kein anderer... - Ermessens-Sache, und wieviel Aufwand man reinstecken mag.

    GerhardW schrieb:

    Der Timestamp sagt aus, wann der Lock verfällt - falls der User seine Eingabe nie beendet.
    Wie kann ich den Lock verfallen lassen ohne den Wert zu ändern?
    Hab ich oben ja erklärt: Wenn ich 9:00 locke, steht 11:00 drin, und der Lock ist spätestens um 11:00:01 verfallen - ohne einen Wert zu ändern.
    Ebenfalls aufgelöst isser, wenn ich da mein Kram einspeicher, und in dem Zuge Date.MinValue reinschreibe.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „ErfinderDesRades“ ()

    Hier geht es doch eher um das Thema das die Benutzer nicht gleichzeitig den gleichen Datensatz bearbeiten und beim speichern der letzte Benutzer die arbeit des anderen Benutzers überschreibt. Das hat nichts damit zu tun ob eine DB für Multiuser Betrieb geeignet ist.

    Also Thema Datensatz z.B. Adresse „Schreibgeschütz“ öffnen.

    Ich hab das mal so gelöst, dass Benutzer ID und Datensatz ID in eine Tabelle „GeöffneteDatensätze“ geschrieben wird. Beim öffnen des Datensatzes (Dialog zum Bearbeiten/Anschauen) wird geschaut ob dieser bereits geöffnet ist. Falls ja, so lässt sich nichts ändern und der Datensatz ist somit „Schreibgeschützt“. Andernfalls wird Benutzer ID und Datensatz ID in die Tabelle GeöffneteDatensätze geschrieben. Beim schließen wird der Eintrag aus der DB wieder entfernt, sofern der Dialog nicht Schreibgeschützt geöffnet ist.
    "Gib einem Mann einen Fisch und du ernährst ihn für einen Tag. Lehre einen Mann zu fischen und du ernährst ihn für sein Leben."

    Wie debugge ich richtig? => Debuggen, Fehler finden und beseitigen
    Wie man VisualStudio nutzt? => VisualStudio richtig nutzen
    Vollzitat des direkten Vorposts an dieser Stelle entfernt ~VaporiZed

    Die Idee gefällt mir auch gut, aber was machst du, wenn ein Datensatz gesperrt wird (in die Tabelle eingetragen) und das Programm abstürzt. Also, das Freigeben nicht mehr stattfindet? Dann muss man manuell die Tabelle löschen?

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „VaporiZed“ ()

    Gibt nen Button um den Datensatz manuell frei zu geben
    "Gib einem Mann einen Fisch und du ernährst ihn für einen Tag. Lehre einen Mann zu fischen und du ernährst ihn für sein Leben."

    Wie debugge ich richtig? => Debuggen, Fehler finden und beseitigen
    Wie man VisualStudio nutzt? => VisualStudio richtig nutzen
    Also ich danke euch für eure Anregungen. Ich habe mich jetzt für folgende Variante entschieden und werde dies einmal testen:

    1) Es gibt in der Datenabse eine Tabelle: LockTable, wo ich mir alle gesperrten Datensätze (Tabelle, ID, PC-Name, Verfallszeit) merke
    2) Soll ein Datensatz zur Bearbeitung geladen werden, wird vorher geprüft, ob dieser in der LockTable vorhanden ist (Tabelle, ID) und ob die Verfallszeit (Datum und Uhrzeit) noch aktuell ist
    Verfallszeit ist gmeint, dass ich die aktuelle Zeit (Datum und Zeit) mit dem Eintrag in der LockTable vergleiche. Bin ich in der "Zukunft" dann darf ich den Eintrag ändern und eine neue Verfallszeit setzen, ansonsten nicht.
    3) Wenn nicht , dann schreibe ich in die LockTable einen Eintrag mit einer Verfallszeit (aktuelle Zeit + Timeout) und den anderen Werten
    4) Wenn ich fertig bin, dann lösche ich den Eintrag wieder und der Datensatz ist freigegeben
    5) Sollte nun ein anderer Benutzer den selben Datensatz verändern möchten, dann beginnt dieser bei Punkt 2)

    Sollte nun bei einem Benutzer, welcher gerade einen Datensatz ändert, ein Programmabsturz kommen, dann bleibt der Eintrag in der LockTable vorhanden. Hierfür gibt es eben die Verfallszeit, ab wann ein anderere Benutzer den Datensatz sperren darf. Eine weitere Möglichkeit wäre den Eintrag manuell über ein Admintool zu löschen. Dies aber nur für besondere Fälle...

    Ich denke, das könnte funktionieren. Oder habt ihr noch Anregungen dazu.

    Dafür möchte ich eine Klasse schreiben, die mir das alles erledigt. Es sollte möglichst einfach sein. Ich weiß nur nicht, ob man hier eine statische Klasse einsetzen soll?

    z.B.
    Die notwendigen Daten (Tabelle, etc.) sollte die Klasse anhand der Form selbtständig ermitteln. Ich denke, da brauche ich dann Reflection Methoden etc...

    Quellcode

    1. Private iRow as New DataRowLock(Me)
    2. ' Vor dem Ändern
    3. if iRow.Lock(ID) then
    4. ' Dantensatz gesperrt
    5. ' Code für Datensatz ändern
    6. ' Am Ende
    7. iRow.Unlock(ID)
    8. else
    9. ' Datensatz konnte nicht gesperrt werden
    10. end if



    Wie ist eure Meinung dazu?

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „GerhardW“ ()

    Jo, eine statische Klasse LockManager.
    Und eine Lock-Methode, die ein IDisposable LockObjekt zurückgibt.
    Wenn du einen Datensatz editieren willst:

    VB.NET-Quellcode

    1. Using LockManager.Lock(myDataRow)
    2. 'myDataRow editieren
    3. End Using
    myDataRow kennt ja ihre DataTable - und damit wären ja alle Informationen gegeben, um einen Lock zu holen.

    Kann man auf die Spitze treiben durch eine DataRow-Extension-Method - dann kommts so raus:

    VB.NET-Quellcode

    1. Using myDataRow.Lock()
    2. 'myDataRow editieren
    3. End Using
    Zu bedenken ist auch, dass du auch sicherstellen musst, dass ParentRows nicht gelöscht werden, während du einen Datensatz editierst.

    Hmm - noch bischen zu kurz gedacht. Man muss ja berücksichtigen, dass ein Lock auch verweigert werden kann. Also:

    VB.NET-Quellcode

    1. Using theLock = myDataRow.Lock()
    2. If not theLock.Success Then Return
    3. 'myDataRow editieren
    4. End Using

    ErfinderDesRades schrieb:

    Und eine Lock-Methode, die ein IDisposable LockObjekt zurückgibt.


    Das weiß ich jetzt nicht, wie das geht? Statische Methoden haben ja kein Dispose oder ? Using setzt aber eine Dispose-Methode voraus oder mache ich eine eine Methode, welche Dispose heißt?
    Sorry, da habe ich zu wenig Kenntnisse...

    EDIT:
    Oder meinst du eine Klasse (LockObject) mit den Eigenschaften des Lock Zustandes, welcher ein IDisposable beinhaltet?

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „GerhardW“ ()

    GerhardW schrieb:

    Soll ein Datensatz zur Bearbeitung geladen werden, wird vorher geprüft, ob dieser in der LockTable vorhanden ist (Tabelle, ID) und ob die Verfallszeit (Datum und Uhrzeit) noch aktuell ist
    Das muss aber granular in einer Transaktion erfolgen und mit einer LockId versehen sein.
    Sonst prüfen zwei Sessions gleichzeitig die Freigabe, bekommen sie beide und locken und verarbeiten beide den Datensatz.
    Es müsste dann ungefähr so aussehen:
    Pseudocode

    SQL-Abfrage

    1. UPDATE LockTable SET LockId={SessionId}, Timeout={Timeout} WHERE Id={RecordId} AND (LockId={SessionId} OR Timeout<{Now})

    Wenn das erfolgreich ist (ein Recordcount von 1 zurückgegeben wird), dann wurde erfolgreich gelockt, andernfalls war ein anderer schneller.
    Der Unlock erfolgt dann etwa so:

    SQL-Abfrage

    1. UPDATE LockTable SET LockId={SessionId}, Timeout={Now} WHERE Id={RecordId} AND LockId={SessionId)

    Das ist die eine Hälfte der Wahrheit.
    Wenn der Datensatz in der LockingTable aber noch nicht existiert, musst du einen INSERT anstatt eines UPDATES ausführen.
    Je nach DB-System geht das unterschiedlich.
    Manche haben einen UPSERT-Befehl, manche arbeiten mit INSERT ... ON DUPLICATE KEY UPDATE ....
    Du kannst dich ja mal durchgoogeln wie ein UPSERT in den von dir verwendeten DB-Systemen aussehen muss.
    Jedenfalls muss auch das granular innerhalb einer Transaktion ablaufen, wenn es wasserdicht sein soll.
    --
    If Not Program.isWorking Then Code.Debug Else Code.DoNotTouch
    --

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „petaod“ ()

    Ich möchte noch mal etwas zur Anregung für dich beitragen. Mit dieser Methode machst du aus einem einfachen Lese/Schreibvorgang, zwei Lese- und drei Schreibvorgänge.

    Quellcode

    1. ​Read Lock Datensatz (existiert Bereits eine Sperre eines anderen Benutzers?)
    2. Write Lock Datensatz (Lock setzen)
    3. Read Datensatz
    4. Write Datensatz
    5. Write Lock Datensatz (Lock freigeben)


    MMn. machst du so deine Anwendung von einer einzigen Tabelle abhängig, die sehr stark frequentiert sein wird, nur um zu prüfen, ob ein Benutzer einen Datensatz zum bearbeiten, oder nur zum lesen öffnen darf. Das kann mit vielen Benutzern sicherlich zum Problem werden.
    Also in der Anwendung gibt es zum Editieren von Datensätzen eigene Eingabefenster. D.h. ich würde, nur wenn ein Benutzer einen Datensatz ändern möchte vorher diese Prüfung machen. Beim Durchblättern/Suchen mache ich nix.
    Nach unseren Erhebungen, wird eher selten etwas geändert, weil wie oft ändert sich eine Kundenadresse? oder Daten von einem Fahrzeug? etc.
    Nachdem aber auf mehreren Standorten Zugriff auf die Daten besteht, müssen wir eine Prüfung implementieren.

    Bisher wurde dies im Programm erst nach der Eingabe der geänderten Daten, also kurz vor dem Update gemacht. Da kann es dann vorkommen, dass die geänderten Daten nicht in die Tabelle geschrieben werden konnten und alles umsonst war. Ich möchte die Prüfung schon vorher machen, sodass der Benutzer gar nicht zur Eingabemaske kommt. Dafür erhält er eine MessageBox, dass der Datensatz vom Benutzer xy gerade bearbeitet wird.

    Also, denke ich, dass das schon klappen sollte...

    ISliceUrPanties schrieb:

    Read Lock Datensatz (existiert Bereits eine Sperre eines anderen Benutzers?)
    Write Lock Datensatz (Lock setzen)
    Wie oben schon erwähnt:
    Diese beiden Vorgänge dürfen nicht in getrennten Transaktionen ablaufen.
    Sonst ist der Lock nicht wasserdicht.
    --
    If Not Program.isWorking Then Code.Debug Else Code.DoNotTouch
    --

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „petaod“ ()