2017-12-06 1 views
0

Ich habe einen Webservice, der fast gleichzeitig mehrere Anfragen erhält.Deadlock in Entity Framework

Wenn dies passiert, die Transaktionen der beiden Anforderungen Deadlock und ich muss eine erneute Ausführung von ihnen Rollback.

 using (var context = new DbContext()) 
     using (var dbContextTransaction = context.Database.BeginTransaction(IsolationLevel.Serializable)) 
     { 
       var subscription = context.Subscriptions.FirstOrDefault(x => x.Provider == provider); 
       if (subscription == null) 
       { 
        */Insert*/ 
       } 
       else 
       { 
        /*Update*/ 
       } 
       context.SaveInTransaction(null); 
       dbContextTransaction.Commit(); 
       } 

Wie ich beide Anfragen verstehe, geben Sie die Transaktion ein und lesen Sie die Abonnements-Tabelle. Zu dem Zeitpunkt, zu dem sie bereit sind, zu committen, sind sie deadlocked, da sie Werte in einem Datenbereich nicht einfügen können, der von einer anderen Transaktion (IsolationLevel.Serializable) gelesen wird.

Wenn ich TransactionLevel nicht serialisieren kann, erfüllt eine der Anforderungen die Unique-Einschränkungen der Provider-Spalte nicht und wird zurückgesetzt.

Was soll ich tun, um Deadlocks zu verhindern?

+0

Sie können mit einem 'Lock' https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/ keywords/lock-statement – Hackerman

+2

Entweder fangen Sie eine Exception, die eine eindeutige Verletzung anzeigt, und versuchen Sie es erneut, oder verwenden Sie raw sql, um "upsert" in einer atomaren Operation auszuführen (in sql server ist die entsprechende Operation MERGE). Mir sind keine anderen Möglichkeiten bekannt, dies in EF zu handhaben. – Evk

Antwort

2

Wenn ich eine der Anfragen nicht das Transaction Serializeable mache, schlägt die einzigartigen Zwänge der Provider-Spalte und bekommt Rollbacked gerecht zu werden.

Sie können:

1) Behandeln Sie die PK Verletzung/Deadlock und gehen die bestehende Zeile zu aktualisieren. Hinweis: Sie sollten READ COMMITTED verwenden und den PK-Verstoß behandeln, da Sie wissen, dass die Sitzung, die den Fehler erhält, mit der Aktualisierung fortfahren kann. Mit SERIALIZABLE können Sie ein Deadlock auf dem INSERT oder dem UPDATE abrufen.

2) Verwenden Sie Raw SQL mit einem Sperrhinweis, damit eine Sitzung auf die andere wartet.

Der korrekte Sperrhinweis ist (UPDLOCK, SERIALIZABLE). Und der Sperrhinweis ist immer noch erforderlich, wenn Sie MERGE verwenden. Siehe https://blogs.msdn.microsoft.com/dbrowne/2013/02/25/why-is-tsql-merge-failing-with-a-primary-key-violation-isnt-it-atomic/

3) Rufen Sie sp_getapplock am Anfang der Transaktion, um sicherzustellen, dass nur eine Sitzung die Sperre hat. Beachten Sie, dass eine CLR-Sperre nur funktioniert, wenn Sie eine einzelne Instanz Ihrer Anwendung haben. Für eine Client/Server-Anwendung oder eine Webfarm funktioniert eine CLR-Sperre nicht.

Sie können eine Methode, um Ihre DbContext etwas wie hinzufügen:

public void GetAppLock(string lockName) 
{ 
    this.Database.ExecuteSqlCommand("exec sp_getapplock @name, 'exclusive'", new System.Data.SqlClient.SqlParameter("@name",lockName)); 
} 
+0

Ist nicht nur SERIALIZABLE genug? Warum wird auch ein updlock benötigt? – Evk

+0

UPDLOCK ist für SELECT erforderlich, da SERIALIZABLE andernfalls S-Sperren verwendet. Für MERGE (oder UPDATE) ist SERIALIZABLE ausreichend, da sie beim Lesen bereits U-Sperren verwenden und Sie nur Bereichssperren für leere Schlüsselbereiche hinzufügen müssen. –

+0

Ah, ich wenn Sie meinen, dass Hinweise für merge erforderlich sind speziell – Evk