Det är lite sent att svara :) men hoppas att det kommer att vara användbart för andra. Svaret innehåller tre delar:
- Vad betyder det "Transaktionskontext som används av en annan session."
- Hur man återskapar felet "Transaktionskontext används av en annan session."
1. Vad betyder det "Transaktionskontext används av en annan session."
Viktigt meddelande:Transaktionskontextlåset förvärvas precis före och släpps omedelbart efter interaktion mellan SqlConnection
och SQL Server.
När du kör en SQL-fråga, SqlConnection
"looks" finns det någon transaktion som omsluter det. Det kan vara SqlTransaction
("native" för SqlConnection) eller Transaction
från System.Transactions
hopsättning.
När transaktionen hittades SqlConnection
använder den för att kommunicera med SQL Server och för tillfället kommunicerar de Transaction
sammanhanget är uteslutande låst.
Vad betyder TransactionScope
? Den skapar Transaction
och tillhandahåller .NET Framework Components-information om det, så alla inklusive SqlConnection kan (och genom design borde) använda det.
Så deklarerar TransactionScope
vi skapar en ny transaktion som är tillgänglig för alla "transaktionsbara" objekt som instansierats i nuvarande Thread
.
Beskrivet fel betyder följande:
- Vi skapade flera
SqlConnections
under sammaTransactionContext
(vilket betyder att de relaterade till samma transaktion) - Vi frågade dessa
SqlConnection
att kommunicera med SQL Server samtidigt - En av dem låste nuvarande
Transaction
sammanhang och nästa gav fel
2. Hur man återskapar felet "Transaktionskontext används av en annan session."
Först och främst används transaktionskontext ("låst") precis vid tidpunkten för exekvering av sql-kommandot. Så det är säkert svårt att reproducera ett sådant beteende.
Men vi kan försöka göra det genom att starta flera trådar som kör relativt långa SQL-operationer under den enda transaktionen. Låt oss förbereda tabellen [dbo].[Persons]
i [tests]
Databas:
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
Och återskapa "Transaktionskontext som används av en annan session." fel med C#-kod baserat på Shrike-kodexempel
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();
}
}
}
}
Och avslutningsvis några ord om att implementera transaktionsstöd i din applikation:
- Undvik flertrådade dataoperationer om det är möjligt (oavsett om du laddar eller sparar). T.ex. spara
SELECT
/UPDATE
/ etc... förfrågningar i en enda kö och servera dem med en entrådsarbetare; - Använd transaktioner i flertrådiga applikationer. Alltid. Överallt. Även för att läsa;
- Dela inte en transaktion mellan flera trådar. Det orsakar konstiga, omedvetna, transcendentala och ej reproducerbara felmeddelanden:
- "Transaktionskontext används av en annan session.":flera samtidiga interaktioner med servern under en transaktion;
- "Timeout har gått ut. Timeoutperioden förflutit innan operationen slutfördes eller så svarar inte servern.":icke beroende transaktioner slutfördes;
- "Transaktionen är osäker.";
- ... och jag antar många andra ...
- Glöm inte att ställa in isoleringsnivå för
TransactionScope
. Standard ärSerializable
men i de flesta fallReadCommitted
räcker; - Glöm inte att Complete()
TransactionScope
ochDependentTransaction