Como usar transações com dapper.net?

106

Eu gostaria de executar várias instruções de inserção em várias tabelas. Estou usando o dapper.net. Não vejo nenhuma maneira de lidar com transações com dapper.net.

Compartilhe suas idéias sobre como usar transações com dapper.net.

Amit
fonte

Respostas:

107

Aqui está o snippet de código:

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

Observe que você precisa adicionar referência ao System.Transactionsassembly porque ele não é referenciado por padrão.

the_joric
fonte
7
É necessário reverter explicitamente o erro ou System.Transactions lida com isso automaticamente?
Norbert Norbertson,
6
@NorbertNorbertson faz isso automaticamente, no Dispose()método. Se Complete()não tiver sido chamado, a transação será revertida.
the_joric
4
Vale a pena mencionar por causa de outra resposta ( stackoverflow.com/a/20047975/47672 ): a conexão deve ser aberta dentro do TransctionScopebloco de uso caso você escolha esta resposta.
0x49D1
2
Veja também ( stackoverflow.com/a/20047975/444469 ) - DoYouDapperWork (Execute, Query, etc ...) precisa da transação nos parâmetros.
Matthieu
O rollback é chamado automaticamente se houver um problema?
gandalf
91

Eu preferi usar uma abordagem mais intuitiva, obtendo a transação diretamente da conexão:

// 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
fonte
@ANeves: Bem, provavelmente estamos usando frameworks Dapper diferentes, porque este tem: github.com/StackExchange/dapper-dot-net
andrecarlucci
25
tem que chamar connection.open () antes de .begintransaction
Timeless
Uma conexão não é automaticamente inscrita no escopo de transações a menos que você abra a conexão dentro do escopo de transações. Não sei como seu código funciona, se GetOpenConnection de alguma forma se abre magicamente dentro do escopo de transações, mas aposto que não
Erik Bergstedt
@ErikBergstedt, você está dizendo que a conexão deve ser aberta somente após a chamada .BeginTransaction()? Se fosse esse o caso, esse método de extensão promoveria o uso incorreto da transação. (IMO, deve até lançar "não é possível abrir a transação depois que a conexão já estiver aberta".)
ANeves
2
Um bom ponto para incluir a transação como um parâmetro no Execute, pois isso é obrigatório.
Arve Systad
19

Você deve ser capaz de usar, TransactionScopejá que o Dapper executa apenas comandos ADO.NET.

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

Considerando que todas as suas tabelas estão em um único banco de dados, discordo da TransactionScopesolução sugerida em algumas respostas aqui. Consulte esta resposta.

  1. TransactionScopegeralmente é usado para transações distribuídas; transações abrangendo bancos de dados diferentes podem estar em sistemas diferentes. Isso precisa de algumas configurações no sistema operacional e no SQL Server, sem as quais não funcionará. Isso não é recomendado se todas as suas consultas forem em uma única instância do banco de dados.
    Mas, com um único banco de dados, isso pode ser útil quando você precisa incluir o código na transação que não está sob seu controle. Com um único banco de dados, também não precisa de configurações especiais.

  2. connection.BeginTransactioné a sintaxe ADO.NET para implementar a transação (em C #, VB.NET etc.) em um único banco de dados. Isso não funciona em vários bancos de dados.

Portanto, connection.BeginTransaction()é o melhor caminho a percorrer.

Mesmo a melhor maneira de lidar com a transação é implementar UnitOfWork conforme explicado nesta resposta.

Amit Joshi
fonte
4
Não são necessários vários bancos de dados para se beneficiar do TransactionScope. De particular utilidade é que é ambiente. É ótimo para agrupar código que você não possui ou não pode modificar em uma transação. Por exemplo, pode ser usado com grande efeito quando o código de teste de unidade / integração que faz chamadas de banco de dados onde você deseja reverter depois. Apenas coloque um TransactionScope, teste o código e descarte durante a limpeza de teste.
Larry Smith
3
@LarrySmith: Concordo; mas a questão não é sobre nada disso. OP apenas diz que deseja inserir em várias tabelas em uma transação. Algumas respostas inclusive a aceita, sugerem o uso TransactionScopeque é ineficiente para o que o OP deseja. Concordo que TransactionScopeé uma boa ferramenta em muitos casos; mas não isso.
Amit Joshi
5

A resposta de Daniel funcionou conforme o esperado para mim. Para completar, aqui está um snippet que demonstra o commit e rollback usando um escopo de transação e dapper:

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
fonte
2
@usr que se resume à preferência pessoal. Prefiro saber a primeira vez que algo deu errado e não ver as declarações do log como lixo. Além disso, minha resposta ainda
agrega
@CodeNaked, primeiro, você entendeu a ordem errada. O bloco catch seria atingido primeiro se houver uma exceção e, em seguida, o fim do escopo de uso. Em segundo lugar, observe esta resposta e o documento do MSDN referenciado: stackoverflow.com/a/5306896/190476 chamar dispose uma segunda vez não é prejudicial, um objeto bem projetado ignora a segunda chamada. O downvote não se justifica!
Sudhanshu Mishra
@dotnetguy - Não tentei comunicar qual Disposemétodo é chamado primeiro ou segundo, apenas que é chamado duas vezes. Quanto ao ponto de que "ligar para o descarte uma segunda vez não é prejudicial", é uma grande suposição. Aprendi que os documentos e as implementações reais muitas vezes não concordam. Mas se você quiser a palavra da Microsoft sobre isso: msdn.microsoft.com/en-us/library/…
CodeNaked
3
Portanto, um aviso de análise de código é o seu motivo para votar negativamente? Isso não torna a resposta errada ou enganosa - é quando um voto negativo é apropriado. Por que você não edita a resposta e propõe uma solução melhor, mantendo a funcionalidade? Stack Overflow tem tudo a ver com ajuda e crítica construtiva.
Sudhanshu Mishra