Es un poco tarde para la respuesta :) pero espero que sea útil para otros. La respuesta consta de tres partes:
- ¿Qué significa "Contexto de transacción en uso por otra sesión".
- Cómo reproducir el error "Contexto de transacción en uso por otra sesión".
Aviso importante:el bloqueo de contexto de transacción se adquiere justo antes y se libera inmediatamente después de la interacción entre SqlConnection
y servidor SQL.
Cuando ejecuta alguna consulta SQL, SqlConnection
"parece" hay alguna transacción que lo envuelva. Puede ser SqlTransaction
("nativo" para SqlConnection) o Transaction
de System.Transactions
asamblea.
Cuando la transacción encontró SqlConnection
lo usa para comunicarse con SQL Server y en el momento en que comunican Transaction
el contexto está bloqueado exclusivamente.
¿Qué hace TransactionScope
? Crea Transaction
y proporciona información sobre los componentes de .NET Framework, por lo que todos, incluido SqlConnection, pueden (y por diseño deben) usarlo.
Declarando TransactionScope
estamos creando una nueva Transacción que está disponible para todos los objetos "transaccionables" instanciados en el Thread
actual .
El error descrito significa lo siguiente:
- Creamos varias
SqlConnections
bajo el mismoTransactionContext
(lo que significa que se relacionaron con la misma transacción) - Les preguntamos a estos
SqlConnection
para comunicarse con SQL Server simultáneamente - Uno de ellos bloqueó la
Transaction
actual contexto y el siguiente arrojó un error
En primer lugar, el contexto de la transacción se usa ("bloqueado") justo en el momento de la ejecución del comando sql. Por lo tanto, es difícil reproducir ese comportamiento con seguridad.
Pero podemos intentar hacerlo iniciando varios subprocesos que ejecutan operaciones SQL relativamente largas en una sola transacción. Preparemos la tabla [dbo].[Persons]
en [tests]
Base de datos:
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
Y reproduzca "Contexto de transacción en uso por otra sesión". error con el código C# basado en el ejemplo de código de 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();
}
}
}
}
Y en conclusión, algunas palabras sobre la implementación del soporte de transacciones en su aplicación:
- Evite las operaciones de datos de subprocesos múltiples si es posible (no importa cargar o guardar). P.ej. guardar
SELECT
/UPDATE
/ etc... solicitudes en una sola cola y atenderlas con un trabajador de un solo subproceso; - En aplicaciones de subprocesos múltiples, use transacciones. Siempre. En todas partes. Incluso para leer;
- No comparta transacciones únicas entre varios subprocesos. Causas extrañas, no obvias, trascendentales y no reproducibles mensajes de error:
- "Contexto de transacción en uso por otra sesión.":múltiples interacciones simultáneas con el servidor en una transacción;
- "El tiempo de espera expiró. El período de tiempo de espera transcurrió antes de completar la operación o el servidor no responde".:no se completaron transacciones dependientes;
- "La transacción está en duda.";
- ... y supongo que muchas otras...
- No olvide establecer el nivel de aislamiento para
TransactionScope
. El valor predeterminado esSerializable
pero en la mayoría de los casosReadCommitted
es suficiente; - No olvide completar()
TransactionScope
yDependentTransaction