Datenbank Anwendung im Netzwerk

  • VB.NET

Es gibt 41 Antworten in diesem Thema. Der letzte Beitrag () ist von DMO.

    GerhardW schrieb:

    Oder meinst du eine Klasse (LockObject) mit den Eigenschaften des Lock Zustandes, welcher ein IDisposable beinhaltet?
    fast.
    Die Eigenschaften des LockObjektes beinhalten kein IDisposable - dassis semantisch Unfug.
    Bei Interfaces gibts nur zwei Möglichkeiten:
    • Eine Klasse implementiert ein Interface
    • Ein anderes Interface beerbt ein Interface
    Ich meine ersteres: Die LockObject-Klasse soll IDisposable implementieren.
    Die DataRow.Lock-Extension-Methode soll ein LockObject erstellen, welches IDisposable ist.
    Das LockObject enthält ausserdem Informationen über den Lock-Hole-Versuch, v.a., obs geklappt hat.
    Die Dispose-Methode des LockObject soll dann in der DB den Lock wieder freigeben.

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

    Prüfst du auch vor dem Update eines Datensatzes ob noch ein Lock besteht?

    Use Case:
    User A lockt einen Datensatz zum Schreiben, Speicher nicht hat aber den Datensatz weiter geöffnet und das Lock läuft ab.

    User B öffnet den gleichen Datensatz und beantragt ein Lock, erhält dieses weil der Lock von User A abgelaufen ist.
    User B speichert seine Änderungen.

    User A fällt auf, dass er seine Änderungen noch nicht gespeichert hat und speichert.
    Die deutsche Sprache ist Freeware, du kannst sie benutzen, ohne dafür zu bezahlen. Sie ist aber nicht Open Source, also darfst du sie nicht verändern, wie es dir gerade passt.

    MrTrebron schrieb:

    Prüfst du auch vor dem Update eines Datensatzes ob noch ein Lock besteht?

    Use Case:
    User A lockt einen Datensatz zum Schreiben, Speicher nicht hat aber den Datensatz weiter geöffnet und das Lock läuft ab.

    User B öffnet den gleichen Datensatz und beantragt ein Lock, erhält dieses weil der Lock von User A abgelaufen ist.
    User B speichert seine Änderungen.

    User A fällt auf, dass er seine Änderungen noch nicht gespeichert hat und speichert.


    Ja, das stimmt. Ich prüfen, ob ich noch innerhalb des Timeout bin. An das habe ich schon gedacht. Ich muss die Gültigkeit des Locking prüfen.

    ErfinderDesRades schrieb:

    Ich meine ersteres: Die LockObject-Klasse soll IDisposable implementieren.
    Die DataRow.Lock-Extension-Methode soll ein LockObject erstellen, welches IDisposable ist.
    Das LockObject enthält ausserdem Informationen über den Lock-Hole-Versuch, v.a., obs geklappt hat.
    Die Dispose-Methode des LockObject soll dann in der DB den Lock wieder freigeben.


    Ist jetzt keine Extension aber ist das so gemeint? Prinzipiell gemeint.

    VB.NET-Quellcode

    1. Public Class MySQLLock
    2. Shared Function Lock(dr as DataRow) As LockInfo
    3. Dim mLock As LockInfo = New LockInfo()
    4. ' Hier erfolgt das Lockingverfahren
    5. ...
    6. Return mLock
    7. End Function
    8. End Class
    9. Public Class LockInfo: Implements IDisposable
    10. Private m_Success As Boolean = False
    11. Public ReadOnly Property Success() As Boolean
    12. Get
    13. Return m_Success
    14. End Get
    15. End Property
    16. Public Sub Dispose() Implements IDisposable.Dispose
    17. ' Hier entsperre ich wieder
    18. End Sub
    19. Public Sub New()
    20. m_Success = False
    21. End Sub
    22. End Class

    Anbei mein Basiscode für das Sperren von Datensätzen in einer MySQL Umgebung. @ErfinderDesRades, ich habe die Klasse nicht wirklich in eine Extension gepackt, weil diese in unserer Anwendung nicht einsetzbar ist. Dennoch habe ich versucht meine Shared Klasse in einer Extension zu benutzen. Vielleicht würde auch das ausreichen.

    1) Die Basis der Shared Klasse ist eine LockInfo Klasse, wo die Locking Informationen ausgelesen werden können bzw. gespeichert werden.

    VB.NET-Quellcode

    1. Option Strict On
    2. ''' <summary>
    3. ''' Lock Informationen zum gesperrten Datensatz
    4. ''' </summary>
    5. Public Class LockInfo: Implements IDisposable
    6. Public Property SessionId As String
    7. Public Property LockId As Integer
    8. Public Property TableName As String
    9. Public Property RecordID as Integer
    10. Public Property UserName as String
    11. Public Property ComputerName As String
    12. Public Property Success As Boolean
    13. Public Property LockTimeout As String
    14. Public Sub New()
    15. SessionId = LockManager.SessionId
    16. LockId = 0
    17. TableName = ""
    18. RecordId = 0
    19. UserName = LockManager.UserName
    20. ComputerName = System.Environment.MachineName
    21. Success = False
    22. LockTimeout = LockManager.GetServerDateTime()
    23. End Sub
    24. ''' <summary>
    25. ''' Dispose: Freigeben des gesperrten Datensatzes
    26. ''' Wird benötigt in einer Using Anweisung
    27. ''' </summary>
    28. Public Sub Dispose() Implements IDisposable.Dispose
    29. LockManager.Unlock(Me)
    30. End Sub
    31. End Class


    2) Das gesamte Management wurde in eine Shared Klasse "LockManager" gepackt. Diese Klasse stellt Methoden zum Sperren (Lock) und Entsperren (Unlock) zur Verfügung. Des Weiteren wurde eine Methode (CheckLock) implementiert, welche vor dem Schreiben in eine Tabelle nochmals aufgerufen werden sollte. Diese Methode prüft, ob das Locking noch gültig ist. Dies ist dann der Fall, wenn der Benutzer einen Datensatz sperrt und inzwischen der Timeout abläuft. Es wird geprüft, ob der Datensatz inzwischen von jemanden anderen gesperrt wurde oder der Benutzer noch immer akutell ist. Wenn ja, dann wird neuerlicht der Datensatz gesperrt ansonsten ist das Locking ungültigt. D.h. die Propertty "Success" wird auf False gesetzt.

    Die Klasse erzeugt automatisch/prüft beim Übergeben einer gültigen MySQLConnection die Locking Tabelle (siehe Grundwerte Punkt 3)
    Damit eine eindeutige Zuordnung der Einträge in der LockingTabelle vorhanden ist, wird eine GUID erzeugt, welche ebenfalls in der LockingTabelle gespeichert wird.

    LOCK Methoden

    VB.NET-Quellcode

    1. ''' <summary>
    2. ''' Sperrt einen Datensatz und gibt eine Lock Information zurück
    3. ''' </summary>
    4. ''' <param name="row">zu sperrende Datensatz</param>
    5. ''' <returns>Lock Informationen</returns>
    6. Overloads Shared Function Lock(row As DataRow) As LockInfo
    7. m_ErrMsg = ""
    8. m_Lock = New LockInfo()
    9. ' Prüfe, ob Initialisierungsflag gesetzt ist - d.h. eine MySqlConnection und die LockManager Tabelle vorhanden sind
    10. If Not InitFlag Then Return m_Lock
    11. ' Lockinformationen zuweisen - Infos vom Datensatz holen
    12. m_Lock.TableName = row.Table.TableName
    13. Dim m_RecordIdField As String = GetPrimaryKeyFieldName(row)
    14. If m_RecordIdField = "" Then
    15. m_ErrMsg = "Kein PrimaryKey Field im DataRow mit AutoIncrement gefunden!"
    16. m_Lock.Success = False
    17. Return m_Lock
    18. End If
    19. m_Lock.RecordID = CInt(row(m_RecordIdField))
    20. ' aktuelle Server DateTime lesen
    21. m_Lock.LockTimeout = GetServerDateTime(True)
    22. If m_Lock.LockTimeout Is Nothing Then
    23. m_Lock.Success = False
    24. Return m_Lock
    25. End If
    26. ' Versuche den Datensatz zu sperren
    27. If TryLock() Then Return m_Lock
    28. m_Lock.Success = False
    29. Return m_Lock
    30. End Function
    31. ''' <summary>
    32. ''' Sperrt einen Datensatz und gibt eine Lock Information zurück
    33. ''' </summary>
    34. ''' <param name="pTableName">Tabellenname</param>
    35. ''' <param name="pRecordId">Id des zu sperrenden Datensatzes</param>
    36. ''' <returns>Lock Informationen</returns>
    37. Overloads Shared Function Lock(pTableName As String, pRecordId As Integer) As LockInfo
    38. m_ErrMsg = ""
    39. m_Lock = New LockInfo()
    40. ' Prüfe, ob Initialisierungsflag gesetzt ist - d.h. eine MySqlConnection und die LockManager Tabelle vorhanden sind
    41. If Not InitFlag Then Return m_Lock
    42. ' Lockinformationen zuweisen
    43. m_Lock.TableName = pTableName
    44. m_Lock.RecordID = pRecordId
    45. ' aktuelle Server DateTime lesen
    46. m_Lock.LockTimeout = GetServerDateTime(True)
    47. If m_Lock.LockTimeout Is Nothing Then
    48. m_Lock.Success = False
    49. Return m_Lock
    50. End If
    51. ' Versuche den Datensatz zu sperren
    52. If TryLock() Then Return m_Lock
    53. m_Lock.Success = False
    54. Return m_Lock
    55. End Function


    CHECKLOCK Methoden

    VB.NET-Quellcode

    1. ''' <summary>
    2. ''' Prüft, ob die Sperrung noch gültig ist
    3. ''' </summary>
    4. ''' <returns>True/False</returns>
    5. Overloads Shared Function CheckLock() As Boolean
    6. m_ErrMsg = ""
    7. ' Prüfe, ob Initialisierungsflag gesetzt ist - d.h. eine MySqlConnection und die LockManager Tabelle vorhanden sind
    8. If Not InitFlag Then Return False
    9. If m_Lock Is Nothing Then Return False
    10. ' Infos von m_Lock verwenden
    11. ' Prüfe, ob der RecordLock noch gültig ist
    12. If Not TryCheckLock() Then Return False
    13. Return True
    14. End Function
    15. ''' <summary>
    16. ''' Prüft, ob die Sperrung noch gültig ist
    17. ''' </summary>
    18. ''' <param name="pLockInfo">Lock Informationen eines gespeicherten Datensatzes</param>
    19. ''' <returns>True/False</returns>
    20. Overloads Shared Function CheckLock(pLockInfo As LockInfo) As Boolean
    21. m_ErrMsg = ""
    22. ' Prüfe, ob Initialisierungsflag gesetzt ist - d.h. eine MySqlConnection und die LockManager Tabelle vorhanden sind
    23. If Not InitFlag Then Return False
    24. If pLockInfo Is Nothing Then Return False
    25. ' Infos von pLockInfo zuweisen
    26. m_Lock = pLockInfo
    27. ' Prüfe, ob der RecordLock noch gültig ist
    28. If Not TryCheckLock() Then Return False
    29. Return True
    30. End Function


    UNLOCK Methoden

    VB.NET-Quellcode

    1. ''' <summary>
    2. ''' Gibt den gesperrten Datensatz wieder frei
    3. ''' </summary>
    4. ''' <returns>True/False</returns>
    5. Overloads Shared Function Unlock() As Boolean
    6. m_ErrMsg = ""
    7. ' Prüfe, ob Initialisierungsflag gesetzt ist - d.h. eine MySqlConnection und die LockManager Tabelle vorhanden sind
    8. If Not InitFlag Then Return False
    9. If m_Lock Is Nothing Then Return True
    10. ' Infos von m_Lock verwenden
    11. ' Datensatz wieder freigeben d.h. aus LockManager Tabelle löschen
    12. If Not Delete() Then Return False
    13. Return True
    14. End Function
    15. ''' <summary>
    16. ''' Gibt den gesperrten Datensatz wieder frei
    17. ''' </summary>
    18. ''' <param name="pLockInfo">Lock Informationen eines gespeicherten Datensatzes</param>
    19. ''' <returns>True/False</returns>
    20. Overloads Shared Function Unlock(pLockInfo As LockInfo) As Boolean
    21. m_ErrMsg = ""
    22. ' Prüfe, ob Initialisierungsflag gesetzt ist - d.h. eine MySqlConnection und die LockManager Tabelle vorhanden sind
    23. If Not InitFlag Then Return False
    24. If pLockInfo Is Nothing Then Return True
    25. ' Infos von pLockInfo zuweisen
    26. m_Lock = pLockInfo
    27. ' Datensatz wieder freigeben d.h. aus LockManager Tabelle löschen
    28. If Not Delete() Then Return False
    29. Return True
    30. End Function



    3) Vor der Verwendung der LockManager Klasse sind einigen Grundwerte zu setzen:

    VB.NET-Quellcode

    1. ' GW: 22.03.2021 - Startwerte für den LockManager (RecordLocking)
    2. ' Benutzername
    3. LockManager.UserName = Benutzername
    4. ' Gültige MySQL Connection
    5. LockManager.Connection = New MySqlConnection(ConnectionString)


    4) So könnten eventuell die Extensions aussehen, wenn mit DataRow gearbeitet wird

    VB.NET-Quellcode

    1. Imports System.Runtime.CompilerServices
    2. ''' <summary>
    3. ''' Extension die DataRow
    4. ''' </summary>
    5. Public Module DataRowExtensions
    6. ''' <summary>
    7. ''' Datensatz sperren
    8. ''' </summary>
    9. ''' <param name="row">Datensatz</param>
    10. ''' <returns>LockInformation</returns>
    11. <Extension()>
    12. Public Function Lock(row As DataRow) As LockInfo
    13. Dim mLockInfo As LockInfo = LockManager.Lock(row)
    14. Return mLockInfo
    15. End Function
    16. ''' <summary>
    17. ''' Datensatz prüfen, ob Sperrung noch gültig (LockTimeout abgelaufen?)
    18. ''' </summary>
    19. ''' <param name="row">Datensatz</param>
    20. ''' <returns>True/False</returns>
    21. <Extension()>
    22. Public Function CheckLock(row As DataRow) As Boolean
    23. If Not LockManager.CompareDataRowWithLockInfo(row) Then Return False
    24. Return LockManager.CheckLock()
    25. End Function
    26. ''' <summary>
    27. ''' Datensatz prüfen, ob Sperrung noch gültig (LockTimeout abgelaufen?)
    28. ''' </summary>
    29. ''' <param name="row">Datensatz</param>
    30. ''' <param name="pLockInfo">Bestimmte LockInformation</param>
    31. ''' <returns>True/False</returns>
    32. <Extension()>
    33. Public Function CheckLock(row As DataRow, pLockInfo As LockInfo) As Boolean
    34. If Not LockManager.CompareDataRowWithLockInfo(row, pLockInfo) Then Return False
    35. Return LockManager.CheckLock(pLockInfo)
    36. End Function
    37. ''' <summary>
    38. ''' Datensatz wieder freigeben
    39. ''' </summary>
    40. ''' <param name="row">Datensatz</param>
    41. ''' <returns>True/False</returns>
    42. <Extension()>
    43. Public Function UnLock(row As DataRow) As Boolean
    44. If Not LockManager.CompareDataRowWithLockInfo(row) Then Return False
    45. Return LockManager.Unlock
    46. End Function
    47. ''' <summary>
    48. ''' Datensatz wieder freigeben
    49. ''' </summary>
    50. ''' <param name="row">Datensatz</param>
    51. ''' <param name="pLockInfo">Bestimmte LockInformation</param>
    52. ''' <returns>True/False</returns>
    53. <Extension()>
    54. Public Function UnLock(row As DataRow, pLockInfo As LockInfo) As Boolean
    55. If Not LockManager.CompareDataRowWithLockInfo(row, pLockInfo) Then Return False
    56. Return LockManager.Unlock(pLockInfo)
    57. End Function
    58. End Module
    Dateien

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „GerhardW“ () aus folgendem Grund: Code wurde nicht richtig kopiert.

    Puh - da müsste ich MySql installieren, und der zip sieht mir auch nicht aus, als sei das eine lauffähige Solution.
    Also du wünschst dir, dass jemand
    • MySql installiert
    • eine Datenbank aufsetzt
    • eine Anwendung schreibt, mit Zugriff auf diese DB
    • deine Lock-Mechanismen da korrekt einfrickelt
    • mit mehreren Instanzen testet
    Jo - da werden sich die Leuts drum reissen. ;)
    Ich wünsche mir eigentlich nur, wenn jemand die Klassen in einem Projekt verwendet, mir eine Rückmeldung zu geben. Das Zip ist kein eingenständiges Programm (Solution). Ist auch nicht so gedacht. Es sind die Klassen als einzelne Dateien und diese sollten jederzeit in ein bestehendes Projekt importiert werden können.

    GerhardW schrieb:

    wenn jemand die Klassen in einem Projekt verwendet

    also ich hätte vor, dass in meiner 2.0-Version meines Projektes zu nutzen, anstatt der Lockinfo's an jeder einzelnen Table - wie ich es aktuell mache.

    Ich gebe dann gerne Rückmeldung ob das so funzt :thumbup:
    "Na, wie ist das Wetter bei dir?"
    "Caps Lock."
    "Hä?"
    "Shift ohne Ende!" :thumbsup:
    @GerhardW: Könntest du bitte ein Codebeispiel geben, wie du das Teil instanziierst und im Anschluss benutzt?
    Ich versuch' das grad alles zu verstehen und vor allem auch für zumindest AccessDB lauffähig zu machen.

    EDIT: wobei, ich hab' mir grad ne kleine Klasse zusammengehämmert - die scheint mir völlig auszureichen und kann ich mit jeder Datenbank nutzen:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Class RecordLock
    2. Private Shared UserRow As BenutzerRow
    3. Private Shared MachineName As String
    4. Private Shared TableName As String
    5. Private Shared Records As Integer()
    6. Private Shared Locked As List(Of RecordLockRow)
    7. ''' <summary>RecordLock initialisieren und Properties setzen</summary>
    8. Public Sub New(table As DataTable, ParamArray recordIDs As Integer())
    9. If recordIDs.Length = 0 Then Throw New Exception("Ohne Record-ID's kein Locking möglich!")
    10. UserRow = User.UserRow
    11. MachineName = Environment.MachineName
    12. TableName = table.TableName
    13. Records = recordIDs
    14. Locked = New List(Of RecordLockRow)
    15. End Sub
    16. ''' <summary>wenn nicht schon ein Lock eines anderen Users für die Datensätze besteht, dann Lock setzen</summary>
    17. Public Function LockRecord() As Boolean
    18. If IsAlreadyLocked() Then Return False
    19. Records.ForEach(Sub(id) Locked.Add(Dts.RecordLock.AddRecordLockRow(UserRow, MachineName, TableName, id, Date.Now)))
    20. Dts.SaveDts() 'lock persistieren
    21. Return True
    22. End Function
    23. ''' <summary>den eigenen RecordLock rausnehmen</summary>
    24. Public Sub UnlockRecord()
    25. Locked.ForEach(Sub(rw) rw.Delete())
    26. Dts.SaveDts() 'unlock persistieren
    27. End Sub
    28. ''' <summary>Refreshen der RecordLock-Tabelle und prüfen, ob bereits ein Lock eines anderen Users besteht</summary>
    29. Private Function IsAlreadyLocked() As Boolean
    30. If Not App.PersistanceModeXml Then Dts.RecordLock.CustomFill("") 'wenn DB-Betrieb, dann muss hier die Tabelle im DataSet mit den Daten aus der Datenbank aktualisiert werden
    31. Dim ToDelete As New List(Of DataRow)
    32. For Each recID In Records
    33. Dim tryLock = Dts.RecordLock.FirstOrDefault(Function(x) x.Tabelle = TableName AndAlso x.DatensatzID = recID)
    34. If tryLock IsNot Nothing Then
    35. If Not tryLock.Timestamp > Date.Now.AddHours(1) Then 'wenn der Lock seit 1 Stunde besteht, dann ist dieser verfallen.
    36. msgError($"Der Datensatz ist seit {tryLock.Timestamp} durch {tryLock.BenutzerRow.Benutzername} | {tryLock.Rechner} gesperrt.")
    37. Return True
    38. Else
    39. ToDelete.Add(tryLock) 'den abgelaufenen Lock löschen
    40. End If
    41. End If
    42. Next
    43. ToDelete.ForEach(Sub(rw) rw.Delete())
    44. Return False
    45. End Function
    46. End Class

    "Na, wie ist das Wetter bei dir?"
    "Caps Lock."
    "Hä?"
    "Shift ohne Ende!" :thumbsup:

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

    ne, das war nur ein Gedankengang von mir, ob man das nicht so machen kann.
    Bei Anwendungsstart werden die benötigten Daten aus der DB ins DataSet gesaugt - danach Zugriff auf DB für weitere Programmteile (jeweils immer ins DataSet).
    Und eben für jeden Lock/Unlock zusätzlich auch ein Öffnen/Schließen der Connection.
    "Na, wie ist das Wetter bei dir?"
    "Caps Lock."
    "Hä?"
    "Shift ohne Ende!" :thumbsup: