C# WPF - EF 5.0 - Pessimistic Locking

UnKnOwN_86

Ensign
Registriert
Apr. 2010
Beiträge
192
Ich habe ein kleines Verständnis Problem mit Pessimistic Locking und hoffe ihr könnt mir helfen.

In dem Projekt verwende ich Entity Framework 5.0 als Data Acccess Layer.
Nun möchte ich gerne im User Interface, ein DataGrid mit Datensätzen ausgeben.

Wenn ich nun einen Datensatz markiere, werden im unteren Bereich des Forms dessen Eigenschaften geladen und in Textboxen/Comboboxen etc. im read-only state angezeigt.

Etwas weiter unterhalb befindet sich dann ein "Edit" Button.

Wenn nun User A diesen Edit Button betätigt möchte ich diesen einen Datensatz (nicht die komplette Tabelle), für weitere Update/Delete operation sperren sowie die Textboxen/Comboboxen etc. zum bearbeiten freigeben (read-only entfernen).

Dies soll wenn möglich so aussehen, dass wenn User B den Datensatz im Grid anklickt der Edit Button bereits gesperrt ist.
Also dass im RowChanged Ereignis eine Überprüfung statt findet ob der gewählte Datensatz gesperrt ist oder nicht und je nachdem den Edit Button freigibt.

Im Internet findet man unzählige Beispiele die ungefähr dieses Schema verwenden:

Code:
using (var scope = new TransactionScope(...))
{
    using (var context = new YourContext(...))
    {
        var customer = 
            context.ExecuteStoreQuery<Customer>("SELECT ... FROM Customers WITH (UPDLOCK) WHERE ...");

        // rest of your logic while record is locked

        scope.Complete();
    }
}

Mein Problem mit diesem ist das dieser Code in einem ausgeführt wird und dies in meinem Szenario nicht der Fall ist.
Da bei mir das Locking des Datensatz beim Klick auf Edit statt finden soll, User A gibt dann Daten und geht Kaffee trinken - kommt 30 Minuten später (im Extrem Fall) und klickt dann erst auf Speichern. Während dieser ganzen Zeit soll der Datensatz zum bearbeiten für User B gesperrt sein.

Zu meinen Fragen:
Wie mache ich diese Trennung von Sperren des Datensatzes und der Update Methode?
Wie kann ich überprüfen ob der jeweilige Datensatz gesperrt ist oder nicht?

Hoffe ihr könnt mir helfen.
 
Zuletzt bearbeitet:
Hallo,

Ich hatte vor einiger Zeit auch damit zu tun Datensätze zu Sperren die gerade in Bearbeitung sind. Da kam ich ach auf die Idee mit Pessimistic Locking aber das erwies sich nicht als Lösung wie wir sie haben wollten.


Unsere Jetzige Lösung ist:
(Bezieht sich auf C# und MySQL)

Wir haben eine neue Datenbanktabelle angeleckt namens "Locked_Datas" die folgende Spalten besitzt.
User_ID
Lock_start_Time
Locked_Row_ID(Entity_ID)
Table_Name(Entity_Name)

Nach dem User A den Datensatz bearbeitet wird wird hier dann, Wer, wann, Datensatz ID und Tabellenname eingetragen.
(Nach Beendigung der Bearbeitung wird der Eintrag wieder gelöscht um den Datensatz zur weitern bearbeitung freigegen wird)

Wenn jetzt User B den gleichen Datensatz bearbeiten möchte wird erst in der Tabelle Locked_Datas nachgeschaut ob der Datensatz gesperrt ist, falls ja bekommt er eine Meldung das User A zurzeit diesen Datensatz bearbeitet wird.
So jetzt weis auch User B wer den Datensatz bearbeiten und kann dann den Kollegen fragen wie lange er noch braucht.


Das war jetzt nur ein Beispiel so wollte es unser Kunde.

Mann kann da natürlich auch noch sehr variieren.



Mfg
 
Da du an einer Desktop-App arbeitest, könntest du das ganze über Threads lösen. Als Isolations-Level würde ich in diesem Fall SERIALIZABLE wählen.

Ein ManualResetEvent in deinem ViewModel oder Klasse zum Fenster erstellen.
Code:
private readonly ManualResetEvent _mre = new ManualResetEvent( false );

Code für den Edit-Button:
Code:
_mre.Reset();
Thread t = new Thread( DoEdit );
t.Start();

Code für den Thread:
Code:
private void DoEdit()
{
  TransactionOptions options = new TransactionOptions { IsolationLevel = IsolationLevel.Serializable };
  using( TransactionScope scope = new TransactionScope( TransactionScopeOption.Required, options ) )
  {
    using ( var context = ... ) 
    {
      var customer = ...; // SELECT
      ...
      _mre.WaitOne();
      ...
      // UPDATE
      ...   
      scope.Complete();
    }
  }
}

Code für den Save-Button:
Code:
_mre.Set();

Das ganze macht es aber deutlich komplizierter, wenn du eine z.B. eine Bearbeitung abbrechen möchtest. Für diesen Fall musst du eine Referenz auf den Thread halten und diesen abbrechen. In DoEdit musst dann zusätzlich auf eine ThreadAbortedException horchen.
Allerdings hast du so auch den Vorteil, dass du mit Timeouts arbeiten kannst. WaitOne nimmt auch einen Int32 oder TimeSpan entgegen und gibt einen bool zurück, der signalisiert ob ein Timeout vorliegt.

Ebenfalls wirst du mit dem Dispatcher arbeiten müssen. (Wenn du es nicht eh schon tust.)

Es ist auch möglich, dass bei zu langem warten, die Verbindung zur Datenbank bereits geschlossen wurde (durch EF). Umgehen lässt sich das, in dem du die Verbindung manuell aufbaust und als Parameter für den Konstruktor deines Kontextes verwendest. Ob das ratsam ist, steht allerdings auf einem anderen Papier ;)
 
Zurück
Oben