È un po' tardi per la risposta :) ma spero che possa essere utile per gli altri. La risposta contiene tre parti:
- Che cosa significa "Contesto della transazione in uso da un'altra sessione."
- Come riprodurre l'errore "Contesto della transazione in uso da un'altra sessione."
Avviso importante:il blocco del contesto della transazione viene acquisito appena prima e rilasciato immediatamente dopo l'interazione tra SqlConnection
e SQL Server.
Quando esegui alcune query SQL, SqlConnection
"sembra" c'è qualche transazione che lo avvolge. Potrebbe essere SqlTransaction
("nativo" per SqlConnection) o Transaction
da System.Transactions
assemblaggio.
Quando la transazione ha trovato SqlConnection
lo usa per comunicare con SQL Server e nel momento in cui comunicano Transaction
il contesto è bloccato esclusivamente.
Cosa significa TransactionScope
? Crea Transaction
e fornisce informazioni sui componenti di .NET Framework a riguardo, in modo che tutti, incluso SqlConnection, possano (e in base alla progettazione dovrebbero) usarlo.
Quindi dichiarando TransactionScope
stiamo creando una nuova Transazione che è disponibile per tutti gli oggetti "transattabili" istanziati nel Thread
corrente .
L'errore descritto significa quanto segue:
- Abbiamo creato diverse
SqlConnections
sotto lo stessoTransactionContext
(il che significa che si riferivano alla stessa transazione) - Abbiamo chiesto questi
SqlConnection
per comunicare con SQL Server contemporaneamente - Uno di loro ha bloccato la
Transaction
corrente contesto e il successivo errore generato
Innanzitutto, il contesto della transazione viene utilizzato ("bloccato") proprio al momento dell'esecuzione del comando sql. Quindi è difficile riprodurre con certezza un comportamento del genere.
Ma possiamo provare a farlo avviando più thread eseguendo operazioni SQL relativamente lunghe nell'ambito della singola transazione. Prepariamo la tabella [dbo].[Persons]
in [tests]
Banca dati:
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
E riprodurre "Contesto della transazione in uso da un'altra sessione". errore con codice C# basato sull'esempio di codice 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();
}
}
}
}
E in conclusione, alcune parole sull'implementazione del supporto alle transazioni nella tua applicazione:
- Evita le operazioni di dati multi-thread, se possibile (indipendentemente dal caricamento o dal salvataggio). Per esempio. salva
SELECT
/UPDATE
/ ecc... richieste in un'unica coda e servirle con un singolo thread di lavoro; - Nelle applicazioni multi-thread utilizzare le transazioni. Sempre. Ovunque. Anche per la lettura;
- Non condividere una singola transazione tra più thread. Provoca strano, non ovvio, trascendentale e non riproducibile messaggi di errore:
- "Contesto della transazione in uso da un'altra sessione.":interazioni multiple simultanee con il server in una transazione;
- "Timeout scaduto. Il periodo di timeout è trascorso prima del completamento dell'operazione o il server non risponde.":transazioni non dipendenti sono state completate;
- "La transazione è in dubbio.";
- ... e presumo molti altri...
- Non dimenticare di impostare il livello di isolamento per
TransactionScope
. L'impostazione predefinita èSerializable
ma nella maggior parte dei casiReadCommitted
è sufficiente; - Non dimenticare di completare()
TransactionScope
eDependentTransaction