Малко е късно за отговор :), но се надявам, че ще бъде полезен за други. Отговорът се състои от три части:
- Какво означава „Контекстът на транзакцията се използва от друга сесия.“
- Как да възпроизведете грешка „Контекстът на транзакцията се използва от друга сесия.“
1. Какво означава „Контекстът на транзакцията се използва от друга сесия.“
Важна забележка:Заключването на контекста на транзакцията се придобива точно преди и се освобождава веднага след взаимодействието между SqlConnections
и SQL Server.
Когато изпълните някаква SQL заявка, SqlConnections
"изглежда" има ли транзакция, която го обвива. Може да е SqlTransaction
("роден" за SqlConnection) или Transaction
от System.Transactions
монтаж.
При намерена транзакция SqlConnections
използва го за комуникация със SQL Server и в момента те комуникират Transaction
контекстът е изключително заключен.
Какво означава TransactionScope
? Той създава Transaction
и предоставя информация за компонентите на .NET Framework за него, така че всеки, включително SqlConnection, може (и по замисъл трябва) да го използва.
И така, деклариране на TransactionScope
ние създаваме нова транзакция, която е достъпна за всички обекти с възможност за транзакции, създадени в текущата Thread
.
Описаната грешка означава следното:
- Създадохме няколко
SqlConnections
под същияTransactionContext
(което означава, че са свързани с една и съща транзакция) - Попитахме тези
SqlConnections
за комуникация със SQL Server едновременно - Един от тях заключи текущата
Transaction
контекст и следващата изведена грешка
2. Как да възпроизведете грешка „Контекстът на транзакцията се използва от друга сесия.“
На първо място, контекстът на транзакция се използва ("заключен") точно по време на изпълнение на sql команда. Така че е трудно да се възпроизведе подобно поведение със сигурност.
Но можем да се опитаме да го направим, като стартираме множество нишки, изпълняващи относително дълги SQL операции под една транзакция. Нека подготвим таблица [dbo].[Persons]
в [tests]
База данни:
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
И възпроизведете „Контекст на транзакция, използван от друга сесия.“ грешка с C# код, базиран на примерен код на 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();
}
}
}
}
И в заключение няколко думи за внедряването на поддръжка на транзакции във вашето приложение:
- Избягвайте операции с многонишкови данни, ако е възможно (без значение зареждане или запис). напр. запишете
SELECT
/UPDATE
/ и т.н.... заявки в една опашка и ги обслужвайте с еднонишков работник; - В многонишкови приложения използвайте транзакции. Винаги. навсякъде. Дори за четене;
- Не споделяйте една транзакция между множество нишки. Причинява странно, неочевидно, трансцендентално и невъзпроизводимо съобщения за грешка:
- „Контекст на транзакция, използван от друга сесия.“:множество едновременни взаимодействия със сървъра при една транзакция;
- „Времето е изтекло. Периодът на изчакване е изтекъл преди завършване на операцията или сървърът не отговаря.“:независещите транзакции са завършени;
- „Транзакцията е под съмнение.“;
- ... и предполагам много други ...
- Не забравяйте да зададете ниво на изолация за
TransactionScope
. По подразбиране еSerializable
но в повечето случаиReadCommitted
е достатъчно; - Не забравяйте да Complete()
TransactionScope
иDependentTransaction