Transaktionen vs. Entity Framework

16. Juni 2010

 

Fast jede Anwendung die Daten ändert wird sich irgendwann mit dem Thema Transaktionen auseinandersetzen müssen. LINQ2SQL (und ADO.NET Entity Framework analog) hat hier eine Eigenheit der man sich bewusst sein sollte.

Wenn man einen LINQ2SQL Datenkontext ohne besondere Vorkehrungen verwendet, ergibt sich bzgl. Transaktionen folgendes Bild:

  • Ein Aufruf an SubmitChanges wird in eine lokale (d.h. Datenbank-) Transaktion gekapselt.
  • Mehrere Aufrufe an SubmitChanges laufen jeweils getrennt in ihrer eigenen Transaktion, also nicht übergreifend transaktional sicher.

Typischerweise baut man bei Bedarf eine Transaktionsklammer über TransactionScope auf um bei mehreren Aufrufen an SubmitChanges bzw. auch über mehrere Aufrufe hinweg transaktional sicher zu arbeiten. Das bringt aber ein kleine Konsequenz mit sich:

  • Mit einem TransactionScope als Klammer laufen alle SubmitChanges in der Regel in einer DTC-Transaktion. Das gilt auch für ein einzelnes(!) SubmitChanges!

Der DTC (Distributed Transaction Coordinator, mdsn) hat normalerweise die Aufgabe, Transaktionen über verschiedene Resourcen hinweg (etwa Datenbank und Message Queue oder auch zwei Datenbanken) zu koordinieren. Das ist hier aber nicht der Fall. Und solange man nicht tatsächlich mehrere Resourcen hat ist die Nutzung des DTC an sich überflüssig. Zudem kann sie zu unerwarteten Problemen führen, wenn im Betrieb der DTC nicht eingeschaltet ist oder keinen Netzzugriff hat. (Auf Entwicklerrechnern ist er üblicherweise verfügbar, man rechnet also nicht mit diesem Problem.)

Der Grund dafür daß der DTC hier überhaupt verwendet wird liegt in der Arbeitsweise von Datenbank-Transaktionen und des LINQ2SQL Datenkontext begründet: Datenbank-Transaktionen sind an die Datenbank-Connection gebunden, sobald eine Connection freigegeben wird aber eine Transaktionsklammer offen bleibt, muß diese Transaktion von einer lokalen zu einer DTC-Transaktion “befördert” werden.
LINQ2SQL tut aber genau das. Die Connection wird intern nur on-demand, d.h. während des SubmitChanges, angefordert und auch sofort wieder freigegeben. Im Sinne skalierbarer Anwendungen ist das genau das gewünschte Verhalten, aber es bringt eben besagte Konsequenz bzgl. Transaktionen mit sich.
Vermeiden läßt sich das indem man explizt context.Connection.Open() aufruft. In diesem Fall honoriert der Datenkontext die Tatsache, dass man selbst die Kontrolle übernommen hat und gibt die Connection nur auf explizites Close() oder im Dispose wieder frei. Somit kann auch eine Transaktion als lokale Transaktion durchgeführt werden.
 
Wenn man den DTC nicht braucht und bewußt nicht verwenden will, dann sollte man den DTC auf den Entwicklerrechnern abschalten (der Service ist normalerweise gestartet). Man hat nämlich kaum eine Chance mitzubekommen, ob der DTC nicht doch irgendwo angezogen wird. Ist der Service down, bekommt man hingegen einen Fehler und wird auf das Problem hingewiesen.