Trochę za późno na odpowiedź :), ale mam nadzieję, że przyda się innym. Odpowiedź składa się z trzech części:
- Co to znaczy „Kontekst transakcji używany przez inną sesję?”
- Jak odtworzyć błąd „Kontekst transakcji używany przez inną sesję”.
1. Co to znaczy „Kontekst transakcji używany przez inną sesję”.
Ważna uwaga:Blokada kontekstu transakcji jest pobierana tuż przed i zwalniana natychmiast po interakcji między SqlConnection i SQL Server.
Kiedy wykonujesz jakieś zapytanie SQL, SqlConnection "wygląda", czy jest jakaś transakcja, która go otacza. Może to być SqlTransaction ("native" dla SqlConnection) lub Transaction z System.Transactions montaż.
Po znalezieniu transakcji SqlConnection używa go do komunikacji z SQL Serverem i w tej chwili komunikuje się Transaction kontekst jest zablokowany na wyłączność.
Co oznacza TransactionScope ? Tworzy Transaction i dostarcza informacji o komponentach .NET Framework, aby każdy, w tym SqlConnection, mógł (i powinien) z niego korzystać.
Więc deklarowanie TransactionScope tworzymy nową transakcję, która jest dostępna dla wszystkich obiektów "transactable" utworzonych w bieżącym Thread .
Opisany błąd oznacza:
- Utworzyliśmy kilka
SqlConnectionspod tym samymTransactionContext(co oznacza, że dotyczyły tej samej transakcji) - Poprosiliśmy te
SqlConnectionkomunikować się z SQL Server jednocześnie - Jeden z nich zablokował bieżącą
Transactionkontekst, a następny wyrzucił błąd
2. Jak odtworzyć błąd „Kontekst transakcji używany przez inną sesję”.
Przede wszystkim kontekst transakcji jest używany ("zablokowany") w momencie wykonania polecenia sql. Więc na pewno trudno jest odtworzyć takie zachowanie.
Ale możemy spróbować to zrobić, uruchamiając wiele wątków wykonujących stosunkowo długie operacje SQL w ramach pojedynczej transakcji. Przygotujmy tabelę [dbo].[Persons] w [tests] Baza danych:
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', 'example@sqldat.com')
SET @Counter = @Counter - 1
END
GO
I odtwórz „Kontekst transakcji używany przez inną sesję”. błąd z kodem C# opartym na przykładzie kodu 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();
}
}
}
}
A na zakończenie kilka słów o wdrożeniu obsługi transakcji w Twojej aplikacji:
- Unikaj wielowątkowych operacji na danych, jeśli to możliwe (bez względu na ładowanie lub zapisywanie). Np. zapisz
SELECT/UPDATE/ etc... żądań w jednej kolejce i obsługuj je za pomocą jednowątkowego pracownika; - W aplikacjach wielowątkowych używaj transakcji. Zawsze. Wszędzie. Nawet do czytania;
- Nie udostępniaj pojedynczej transakcji między wieloma wątkami. Powoduje dziwne, nieoczywiste, transcendentalne i nieodtwarzalne komunikaty o błędach:
- "Kontekst transakcji używany przez inną sesję":wiele jednoczesnych interakcji z serwerem w ramach jednej transakcji;
- "Upłynął limit czasu. Limit czasu, który upłynął przed zakończeniem operacji lub serwer nie odpowiada.":zakończone zostały niezależne transakcje;
- "Transakcja jest wątpliwa.";
- ... i zakładam wiele innych...
- Nie zapomnij ustawić poziomu izolacji dla
TransactionScope. Wartość domyślna toSerializableale w większości przypadkówReadCommittedwystarczy; - Nie zapomnij o funkcji Complete()
TransactionScopeiDependentTransaction