É um pouco tarde para responder :) mas espero que seja útil para outros. A resposta contém três partes:
- O que significa "Contexto de transação em uso por outra sessão?"
- Como reproduzir o erro "Contexto de transação em uso por outra sessão."
1. O que significa "Contexto de transação em uso por outra sessão."
Aviso importante:o bloqueio de contexto de transação é adquirido imediatamente antes e liberado imediatamente após a interação entre
SqlConnection
e SQL Server. Quando você executa alguma consulta SQL,
SqlConnection
"looks" existe alguma transação envolvendo-o. Pode ser SqlTransaction
("nativo" para SqlConnection) ou Transaction
de System.Transactions
conjunto. Quando a transação encontrou
SqlConnection
usa para se comunicar com o SQL Server e no momento eles comunicam Transaction
contexto é exclusivamente bloqueado. O que
TransactionScope
? Ele cria Transaction
e fornece informações sobre o .NET Framework Components, para que todos, incluindo SqlConnection, possam (e, por design, devem) usá-lo. Então, declarando
TransactionScope
estamos criando uma nova transação que está disponível para todos os objetos "transacionáveis" instanciados no Thread
atual . Erro descrito significa o seguinte:
- Criamos várias
SqlConnections
sob o mesmoTransactionContext
(o que significa que eles estão relacionados à mesma transação) - Perguntamos a estes
SqlConnection
para se comunicar com o SQL Server simultaneamente - Um deles bloqueou a
Transaction
atual contexto e o próximo erro lançado
2. Como reproduzir o erro "Contexto de transação em uso por outra sessão."
Em primeiro lugar, o contexto da transação é usado ("bloqueado") no momento da execução do comando sql. Portanto, é difícil reproduzir esse comportamento com certeza.
Mas podemos tentar fazer isso iniciando vários threads executando operações SQL relativamente longas em uma única transação. Vamos preparar a tabela
[dbo].[Persons]
em [tests]
Base de dados: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 reproduza "Contexto de transação em uso por outra sessão". erro com código C# baseado no exemplo de código 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, para concluir, algumas palavras sobre a implementação do suporte a transações em seu aplicativo:
- Evite operações de dados multithread se for possível (independentemente de carregar ou salvar). Por exemplo. salve
SELECT
/UPDATE
/ etc... solicitações em uma única fila e atende-as com um trabalhador de thread único; - Em aplicativos multithread, use transações. Sempre. Em toda parte. Mesmo para leitura;
- Não compartilhe uma única transação entre vários tópicos. Causa estranhas, não óbvias, transcendentais e não reproduzíveis mensagens de erro:
- "Contexto de transação em uso por outra sessão.":várias interações simultâneas com o servidor em uma transação;
- "Tempo limite expirou. O período de tempo limite decorreu antes da conclusão da operação ou o servidor não está respondendo.":transações não dependentes foram concluídas;
- "A transação está em dúvida.";
- ... e presumo que muitas outras...
- Não se esqueça de definir o nível de isolamento para
TransactionScope
. O padrão éSerializable
mas na maioria dos casosReadCommitted
é suficiente; - Não se esqueça de Complete()
TransactionScope
eDependentTransaction