Il est un peu tard pour répondre :) mais j'espère que cela sera utile pour les autres. La réponse contient trois parties :
- Que signifie "Contexte de transaction utilisé par une autre session".
- Comment reproduire l'erreur "Contexte de transaction utilisé par une autre session."
Avis important :le verrou de contexte de transaction est acquis juste avant et libéré immédiatement après l'interaction entre SqlConnection
et SQL Server.
Lorsque vous exécutez une requête SQL, SqlConnection
"regarde" y a-t-il une transaction qui l'enveloppe. Il peut s'agir de SqlTransaction
("natif" pour SqlConnection) ou Transaction
de System.Transactions
Assemblée.
Lorsque la transaction a trouvé SqlConnection
l'utilise pour communiquer avec SQL Server et au moment où ils communiquent Transaction
le contexte est exclusivement verrouillé.
Que fait TransactionScope
? Il crée Transaction
et fournit des informations sur les composants .NET Framework à ce sujet, afin que tout le monde, y compris SqlConnection, puisse (et par conception devrait) l'utiliser.
Donc, en déclarant TransactionScope
nous créons une nouvelle Transaction qui est disponible pour tous les objets "transactables" instanciés dans le Thread
actuel .
L'erreur décrite signifie ce qui suit :
- Nous avons créé plusieurs
SqlConnections
sous le mêmeTransactionContext
(ce qui signifie qu'ils sont liés à la même transaction) - Nous avons demandé à ces
SqlConnection
pour communiquer avec SQL Server simultanément - L'un d'eux a verrouillé la
Transaction
actuelle contexte et la prochaine erreur renvoyée
Tout d'abord, le contexte de transaction est utilisé ("verrouillé") juste au moment de l'exécution de la commande sql. Il est donc difficile de reproduire un tel comportement à coup sûr.
Mais nous pouvons essayer de le faire en démarrant plusieurs threads exécutant des opérations SQL relativement longues sous la même transaction. Préparons la table [dbo].[Persons]
dans [tests]
Base de données :
USE [tests]
GO
DROP TABLE [dbo].[Persons]
GO
CREATE TABLE [dbo].[Persons](
[Id] [bigint] IDENTITY(1,1) NOT NULL PRIMARY KEY,
[Name] [nvarchar](1024) NOT NULL,
[Nick] [nvarchar](1024) NOT NULL,
[Email] [nvarchar](1024) NOT NULL)
GO
DECLARE @Counter INT
SET @Counter = 500
WHILE (@Counter > 0) BEGIN
INSERT [dbo].[Persons] ([Name], [Nick], [Email])
VALUES ('Sheev Palpatine', 'DarthSidious', '[email protected]')
SET @Counter = @Counter - 1
END
GO
Et reproduisez "Contexte de transaction utilisé par une autre session". erreur avec le code C# basé sur l'exemple de code Shrike
using System;
using System.Collections.Generic;
using System.Threading;
using System.Transactions;
using System.Data.SqlClient;
namespace SO.SQL.Transactions
{
public static class TxContextInUseRepro
{
const int Iterations = 100;
const int ThreadCount = 10;
const int MaxThreadSleep = 50;
const string ConnectionString = "Initial Catalog=tests;Data Source=.;" +
"User ID=testUser;PWD=Qwerty12;";
static readonly Random Rnd = new Random();
public static void Main()
{
var txOptions = new TransactionOptions();
txOptions.IsolationLevel = IsolationLevel.ReadCommitted;
using (var ctx = new TransactionScope(
TransactionScopeOption.Required, txOptions))
{
var current = Transaction.Current;
DependentTransaction dtx = current.DependentClone(
DependentCloneOption.BlockCommitUntilComplete);
for (int i = 0; i < Iterations; i++)
{
// make the transaction distributed
using (SqlConnection con1 = new SqlConnection(ConnectionString))
using (SqlConnection con2 = new SqlConnection(ConnectionString))
{
con1.Open();
con2.Open();
}
var threads = new List<Thread>();
for (int j = 0; j < ThreadCount; j++)
{
Thread t1 = new Thread(o => WorkCallback(dtx));
threads.Add(t1);
t1.Start();
}
for (int j = 0; j < ThreadCount; j++)
threads[j].Join();
}
dtx.Complete();
ctx.Complete();
}
}
private static void WorkCallback(DependentTransaction dtx)
{
using (var txScope1 = new TransactionScope(dtx))
{
using (SqlConnection con2 = new SqlConnection(ConnectionString))
{
Thread.Sleep(Rnd.Next(MaxThreadSleep));
con2.Open();
using (var cmd = new SqlCommand("SELECT * FROM [dbo].[Persons]", con2))
using (cmd.ExecuteReader()) { } // simply recieve data
}
txScope1.Complete();
}
}
}
}
Et pour conclure, quelques mots sur l'implémentation du support des transactions dans votre application :
- Évitez les opérations de données multithread si c'est possible (peu importe le chargement ou l'enregistrement). Par exemple. enregistrer
SELECT
/UPDATE
/ etc... requêtes dans une seule file d'attente et les traiter avec un seul thread worker ; - Dans les applications multithread, utilisez des transactions. Toujours. Partout. Même pour la lecture ;
- Ne partagez pas une seule transaction entre plusieurs threads. Cela provoque des causes étranges, non évidentes, transcendantales et non reproductibles messages d'erreur :
- "Contexte de transaction utilisé par une autre session." :plusieurs interactions simultanées avec le serveur dans le cadre d'une transaction ;
- "Timeout expiré. Le délai d'attente s'est écoulé avant la fin de l'opération ou le serveur ne répond pas." :aucune transaction dépendante n'a été effectuée ;
- "La transaction est incertaine." ;
- ... et je suppose que beaucoup d'autres...
- N'oubliez pas de définir le niveau d'isolement pour
TransactionScope
. La valeur par défaut estSerializable
mais dans la plupart des casReadCommitted
est suffisant ; - N'oubliez pas de Complete()
TransactionScope
etDependentTransaction