¿Cómo utilizar las transacciones con dapper.net?

106

Me gustaría ejecutar varias declaraciones de inserción en varias tablas. Estoy usando dapper.net. No veo ninguna forma de manejar transacciones con dapper.net.

Comparta sus ideas sobre cómo utilizar las transacciones con dapper.net.

Amit
fuente

Respuestas:

107

Aquí el fragmento de código:

using System.Transactions;    
....    
using (var transactionScope = new TransactionScope())
{
    DoYourDapperWork();
    transactionScope.Complete();
}

Tenga en cuenta que debe agregar una referencia al System.Transactionsensamblado porque no se hace referencia a él de forma predeterminada.

the_joric
fuente
7
¿Es necesario revertir explícitamente el error o System.Transactions lo maneja automáticamente?
Norbert Norbertson
6
@NorbertNorbertson lo hace automáticamente, en Dispose()método. Si Complete()no se ha llamado, la transacción se revierte.
the_joric
4
Vale la pena mencionarlo debido a otra respuesta ( stackoverflow.com/a/20047975/47672 ): la conexión debe abrirse dentro del TransctionScopebloque de uso en caso de que elija esta respuesta.
0x49D1
2
Consulte también ( stackoverflow.com/a/20047975/444469 ) - DoYouDapperWork (Execute, Query, etc ...) necesita la transacción en los parámetros.
Matthieu
¿Se llama automáticamente a la reversión si hay un problema?
gandalf
91

Preferí usar un enfoque más intuitivo obteniendo la transacción directamente desde la conexión:

// This called method will get a connection, and open it if it's not yet open.
using (var connection = GetOpenConnection())
using (var transaction = connection.BeginTransaction())
{
    connection.Execute(
        "INSERT INTO data(Foo, Bar) values (@Foo, @Bar);", listOf5000Items, transaction);
    transaction.Commit();
}
ANeves
fuente
@ANeves: Bueno, probablemente estemos usando diferentes marcos Dapper, porque este tiene: github.com/StackExchange/dapper-dot-net
andrecarlucci
25
tengo que llamar a connection.open () antes de .begintransaction
Timeless
Una conexión no se alista automáticamente en el alcance de la transacción a menos que abra la conexión dentro del alcance de la transacción. No sé cómo funciona su código, si GetOpenConnection de alguna manera se abre mágicamente dentro del alcance de la transacción, pero apuesto a que no es así
Erik Bergstedt
@ErikBergstedt, ¿estás diciendo que la conexión debe estar abierta solo después de que la llamemos .BeginTransaction()? Si ese fuera el caso, este método de extensión promovería el uso incorrecto de la transacción. (OMI, aún debe lanzar "no puede transacción abierta después de la conexión ya está abierta".)
ANeves
2
Buen punto para incluir la transacción como parámetro Execute, ya que es obligatorio.
Arve Systad
19

Debería poder usarlo, TransactionScopeya que Dapper solo ejecuta comandos ADO.NET.

using (var scope = new TransactionScope())
{
   // insert
   // insert
   scope.Complete();
}
Daniel A. White
fuente
8

Teniendo en cuenta que todas sus tablas están en una sola base de datos, no estoy de acuerdo con la TransactionScopesolución sugerida en algunas respuestas aquí. Consulte esta respuesta.

  1. TransactionScopese utiliza generalmente para transacciones distribuidas; la transacción que abarca diferentes bases de datos puede estar en un sistema diferente. Esto necesita algunas configuraciones en el sistema operativo y SQL Server sin las cuales esto no funcionará. Esto no se recomienda si todas sus consultas están relacionadas con una sola instancia de la base de datos.
    Pero, con una sola base de datos, esto puede ser útil cuando necesita incluir el código en una transacción que no está bajo su control. Con una única base de datos, tampoco necesita configuraciones especiales.

  2. connection.BeginTransactiones la sintaxis de ADO.NET para implementar transacciones (en C #, VB.NET, etc.) contra una sola base de datos. Esto no funciona en varias bases de datos.

Entonces, connection.BeginTransaction()es una mejor manera de hacerlo.

Incluso la mejor manera de manejar la transacción es implementar UnitOfWork como se explica en esta respuesta.

Amit Joshi
fuente
4
No se necesitan varias bases de datos para beneficiarse de TransactionScope. De particular utilidad es que sea ambiental. Es ideal para envolver código que no es de su propiedad o que no puede modificar en una transacción. Por ejemplo, se puede usar con gran efecto cuando se trata de un código de prueba de unidad / integración que realiza llamadas a la base de datos donde desea retroceder después. Simplemente haga flotar un TransactionScope, pruebe el código y deséchelo durante la limpieza de prueba.
Larry Smith
3
@LarrySmith: De acuerdo; pero la pregunta no se trata de nada de esto. OP simplemente dice que quiere insertar varias tablas en una transacción. Algunas respuestas, incluida la aceptada, sugieren usar TransactionScopecuál es ineficiente para lo que OP quiere. Estoy de acuerdo en que TransactionScopees una buena herramienta en muchos casos; pero no esto.
Amit Joshi
5

La respuesta de Daniel funcionó como se esperaba para mí. Para completar, aquí hay un fragmento que demuestra la confirmación y la reversión usando un alcance de transacción y elegante:

using System.Transactions;
    // _sqlConnection has been opened elsewhere in preceeding code 
    using (var transactionScope = new TransactionScope())
    {
        try
        {
            long result = _sqlConnection.ExecuteScalar<long>(sqlString, new {Param1 = 1, Param2 = "string"});

            transactionScope.Complete();
        }
        catch (Exception exception)
        {
            // Logger initialized elsewhere in code
            _logger.Error(exception, $"Error encountered whilst executing  SQL: {sqlString}, Message: {exception.Message}")

            // re-throw to let the caller know
            throw;
        }
    } // This is where Dispose is called 
Sudhanshu Mishra
fuente
2
@usr que se reduce a preferencias personales. Prefiero saber la primera vez que algo salió mal y no veo las declaraciones de registro como basura. Además, mi respuesta sigue siendo valiosa al demostrar una forma de usar transacciones con dapper
Sudhanshu Mishra
@CodeNaked, primero, tienes el orden incorrecto allí. El bloque de captura se activará primero si hay una excepción, luego el final del alcance de uso. En segundo lugar, mire esta respuesta y el documento de MSDN al que se hace referencia: stackoverflow.com/a/5306896/190476 llamar a dispose por segunda vez no es dañino, un objeto bien diseñado ignora la segunda llamada. ¡El voto negativo no está justificado!
Sudhanshu Mishra
@dotnetguy: no intenté comunicar qué Disposemétodo se llama primero o segundo, solo que se llama dos veces. En cuanto al punto de que "llamar a desechar por segunda vez no es perjudicial", es una gran suposición. He aprendido que los documentos y las implementaciones reales a menudo no concuerdan. Pero si desea la palabra de Microsoft para ello: msdn.microsoft.com/en-us/library/…
CodeNaked
3
Entonces, ¿una advertencia de análisis de código es su razón para rechazar? Eso no hace que la respuesta sea incorrecta o engañosa, es entonces cuando es apropiado un voto negativo. ¿Por qué no edita la respuesta y propone una mejor solución manteniendo la funcionalidad? Stack overflow tiene que ver con la ayuda y la crítica constructiva.
Sudhanshu Mishra