Na odpověď je trochu pozdě :), ale doufám, že to bude užitečné pro ostatní. Odpověď obsahuje tři části:
- Co to znamená "Kontext transakce je používán jinou relací?"
- Jak reprodukovat chybu „Kontext transakce je používán jinou relací.“
1. Co to znamená „Kontext transakce je používán jinou relací.“
Důležité upozornění:Uzamčení kontextu transakce je získáno těsně před a uvolněno ihned po interakci mezi SqlConnection
a SQL Server.
Když spustíte nějaký SQL dotaz, SqlConnection
"vypadá" je tam nějaká transakce, která to obaluje. Může to být SqlTransaction
("nativní" pro SqlConnection) nebo Transaction
z System.Transactions
shromáždění.
Když byla transakce nalezena SqlConnection
používá jej ke komunikaci s SQL Serverem a v tuto chvíli komunikuje Transaction
kontext je výhradně uzamčen.
Co znamená TransactionScope
? Vytvoří Transaction
a poskytuje o něm informace .NET Framework Components, takže jej může používat každý, včetně SqlConnection (a podle návrhu by měl).
Takže deklaruji TransactionScope
vytváříme novou transakci, která je dostupná všem "transakovatelným" objektům vytvořeným v aktuálním Thread
.
Popsaná chyba znamená následující:
- Vytvořili jsme několik
SqlConnections
pod stejnýmTransactionContext
(což znamená, že se týkají stejné transakce) - Zeptali jsme se těchto
SqlConnection
komunikovat se serverem SQL současně - Jeden z nich uzamkl aktuální
Transaction
kontextu a další vyhodila chybu
2. Jak reprodukovat chybu „Kontext transakce je používán jinou relací.“
Za prvé, transakční kontext je použit („uzamčen“) přímo v okamžiku provádění příkazu sql. Je tedy obtížné takové chování s jistotou reprodukovat.
Můžeme to ale zkusit udělat tak, že spustíme více vláken s relativně dlouhými SQL operacemi v rámci jedné transakce. Připravme si tabulku [dbo].[Persons]
v [tests]
Databáze:
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
A reprodukovat "Kontext transakce používaný jinou relací." chyba s kódem C# na základě příkladu kódu 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 závěr pár slov o implementaci podpory transakcí ve vaší aplikaci:
- Pokud je to možné, vyhněte se vícevláknovým datovým operacím (bez ohledu na načítání nebo ukládání). Např. uložit
SELECT
/UPDATE
/ atd... požadavky v jedné frontě a obsluhují je pomocí jednoho vlákna; - Ve vícevláknových aplikacích používejte transakce. Vždy. Všude. Dokonce i na čtení;
- Nesdílejte jednu transakci mezi více vlákny. Způsobuje to podivné, nezjevné, transcendentální a nereprodukovatelné chybové zprávy:
- "Kontext transakce je používán jinou relací.":několik současných interakcí se serverem v rámci jedné transakce;
- "Časový limit vypršel. Časový limit uplynul před dokončením operace nebo server neodpovídá.":nezávislé transakce byly dokončeny;
- "Transakce je nejistá.";
- ... a předpokládám, že mnoho dalších ...
- Nezapomeňte nastavit úroveň izolace pro
TransactionScope
. Výchozí hodnota jeSerializable
ale ve většině případůReadCommitted
stačí; - Nezapomeňte na Complete()
TransactionScope
aDependentTransaction