c# - framework - TransactionScope no cancela la transacción
transactionscope vb (5)
Aquí está la arquitectura actual de mi código fuente de alcance de transacción. El tercer inserto arroja una excepción .NET (No es una Excepción de SQL) y no está retrotrayendo las dos instrucciones de inserción anteriores. ¿Que estoy haciendo mal?
EDITAR: Eliminé el try / catch de insert2 e insert3. También eliminé la utilidad de manejo de excepciones de insert1 try / catch y puse "throw ex". Todavía no revierte la transacción.
EDIT 2: Agregué el try / catch en el método Insert3 y simplemente coloqué un "throw" en la declaración catch. Todavía no revierte la transacción.
ACTUALIZACIÓN: en función de los comentarios que recibí, la clase "SqlHelper" está utilizando el objeto SqlConnection para establecer una conexión con la base de datos, luego crea un objeto SqlCommand, establece la propiedad CommandType en "StoredProcedure" y llama al método ExecuteNonQuery de SqlCommand.
Tampoco agregué Binding de transacción = Desvincular explícitamente a la cadena de conexión actual. Añadiré eso durante mi próxima prueba.
public void InsertStuff()
{
try
{
using(TransactionScope ts = new TransactionScope())
{
//perform insert 1
using(SqlHelper sh = new SqlHelper())
{
SqlParameter[] sp = { /* create parameters for first insert */ };
sh.Insert("MyInsert1", sp);
}
//perform insert 2
this.Insert2();
//perform insert 3 - breaks here!!!!!
this.Insert3();
ts.Complete();
}
}
catch(Exception ex)
{
throw ex;
}
}
public void Insert2()
{
//perform insert 2
using(SqlHelper sh = new SqlHelper())
{
SqlParameter[] sp = { /* create parameters for second insert */ };
sh.Insert("MyInsert2", sp);
}
}
public void Insert3()
{
//perform insert 3
using(SqlHelper sh = new SqlHelper())
{
SqlParameter[] sp = { /*create parameters for third insert */ };
sh.Insert("MyInsert3", sp);
}
}
Parece que está capturando la excepción en Insert3 () para que su código continúe después de la llamada. Si desea que se retrotraiga, deberá permitir que la excepción suba al bloque try / catch en la rutina principal para que nunca se llame a la instrucción ts.Complete ().
Una reversión implícita solo ocurrirá si se sale del uso sin llamar a ts.complete. Debido a que está manejando la excepción en Insert3 (), la excepción nunca causa una instrucción using para salir.
O vuelva a lanzar la excepción o notifique a la persona que llama que se necesita una reversión (cambie la firma de Insert3 () a bool Insert3 ()?)
También me encontré con un problema similar. Mi problema ocurrió porque la SqlConnection que utilicé en mis SqlCommands ya estaba abierta antes de que se creara TransactionScope, por lo que nunca se enlistó en TransactionScope como transacción.
¿Es posible que la clase SqlHelper esté reutilizando una instancia de SqlConnection abierta antes de ingresar a su bloque de TransactionScope?
No veo su clase de ayuda, sino el retroceso del alcance de la transacción si no llama a la declaración completa incluso si obtiene un error del código .NET. Copié un ejemplo para ti. Puede estar haciendo algo mal en la depuración. Este ejemplo tiene un error en el código .net y un bloque catch similar al tuyo.
private static readonly string _connectionString = ConnectionString.GetDbConnection();
private const string inserttStr = @"INSERT INTO dbo.testTable (col1) VALUES(@test);";
/// <summary>
/// Execute command on DBMS.
/// </summary>
/// <param name="command">Command to execute.</param>
private void ExecuteNonQuery(IDbCommand command)
{
if (command == null)
throw new ArgumentNullException("Parameter ''command'' can''t be null!");
using (IDbConnection connection = new SqlConnection(_connectionString))
{
command.Connection = connection;
connection.Open();
command.ExecuteNonQuery();
}
}
public void FirstMethod()
{
IDbCommand command = new SqlCommand(inserttStr);
command.Parameters.Add(new SqlParameter("@test", "Hello1"));
ExecuteNonQuery(command);
}
public void SecondMethod()
{
IDbCommand command = new SqlCommand(inserttStr);
command.Parameters.Add(new SqlParameter("@test", "Hello2"));
ExecuteNonQuery(command);
}
public void ThirdMethodCauseNetException()
{
IDbCommand command = new SqlCommand(inserttStr);
command.Parameters.Add(new SqlParameter("@test", "Hello3"));
ExecuteNonQuery(command);
int a = 0;
int b = 1/a;
}
public void MainWrap()
{
TransactionOptions tso = new TransactionOptions();
tso.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
//TransactionScopeOption.Required, tso
try
{
using (TransactionScope sc = new TransactionScope())
{
FirstMethod();
SecondMethod();
ThirdMethodCauseNetException();
sc.Complete();
}
}
catch (Exception ex)
{
logger.ErrorException("eee ",ex);
}
}
Si desea depurar sus transacciones, puede usar esta secuencia de comandos para ver bloqueos y estado de espera, etc.
SELECT
request_session_id AS spid,
CASE transaction_isolation_level
WHEN 0 THEN ''Unspecified''
WHEN 1 THEN ''ReadUncomitted''
WHEN 2 THEN ''Readcomitted''
WHEN 3 THEN ''Repeatable''
WHEN 4 THEN ''Serializable''
WHEN 5 THEN ''Snapshot'' END AS TRANSACTION_ISOLATION_LEVEL ,
resource_type AS restype,
resource_database_id AS dbid,
DB_NAME(resource_database_id) as DBNAME,
resource_description AS res,
resource_associated_entity_id AS resid,
CASE
when resource_type = ''OBJECT'' then OBJECT_NAME( resource_associated_entity_id)
ELSE ''N/A''
END as ObjectName,
request_mode AS mode,
request_status AS status
FROM sys.dm_tran_locks l
left join sys.dm_exec_sessions s on l.request_session_id = s.session_id
where resource_database_id = 24
order by spid, restype, dbname;
Verá un SPID para dos llamadas al método antes de llamar al método de excepción.
El nivel de aislamiento predeterminado es serializable. Puede leer más sobre bloqueos y transacciones aquí
(basado en la versión editada que no traga excepciones)
¿Cuánto tiempo tardan las operaciones? Si alguno de ellos tiene una ejecución muy larga, es posible que la característica de error de vinculación de transacción lo haya mordido, es decir, que la conexión se haya desconectado. Intente agregar Transaction Binding=Explicit Unbind
a la cadena de conexión.