152

I would like to run multiple insert statements on multiple tables. I am using dapper.net. I don't see any way to handle transactions with dapper.net.

Please share your ideas on how to use transactions with dapper.net.

Amit Joshi
  • 15,448
  • 21
  • 77
  • 141
Amit
  • 25,106
  • 25
  • 75
  • 116

6 Answers6

133

Here the code snippet:

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

Note that you need to add reference to System.Transactions assembly because it is not referenced by default.

the_joric
  • 11,986
  • 6
  • 36
  • 57
  • 7
    Is it necessary to explicitly roll back on error or does System.Transactions handle that automatically? – Norbert Norbertson Oct 03 '17 at 12:30
  • 10
    @NorbertNorbertson it does it automatically, in `Dispose()` method. If `Complete()` has not been called, transaction gets rolled back. – the_joric Oct 04 '17 at 08:58
  • 6
    Worth to mention because of another answer (https://stackoverflow.com/a/20047975/47672): connection must be opened inside of `TransctionScope` using block in case you choose this answer. – 0x49D1 Jun 08 '18 at 12:00
  • 4
    See also (https://stackoverflow.com/a/20047975/444469) - DoYouDapperWork (Execute, Query, etc...) needs the transaction in the parameters. – Matthieu Jul 18 '18 at 18:03
  • Is the rollback called automatically if there is a problem? – gandalf Mar 08 '19 at 16:18
  • @gandalf Yes, it is. If Complete() is not called, the whole transaction gets rolled back. – James Poulose Mar 23 '20 at 03:42
  • 2
    Does this work if your DoYourDapperWork() use multiple SqlConnections to do the work? For example, let's say I have a dapper repository where each method uses a new connection. Can I call several of those wrapped a TransactionScope? – Ε Г И І И О May 23 '21 at 15:12
125

I preferred to use a more intuitive approach by getting the transaction directly from the connection:

// 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
  • 6,219
  • 3
  • 39
  • 63
  • @ANeves: Well, we're probably using different Dapper frameworks, because this one has: https://github.com/StackExchange/dapper-dot-net – andrecarlucci Nov 28 '14 at 18:28
  • 37
    have to call connection.open() before .begintransaction – Timeless Jun 05 '15 at 09:09
  • 1
    A connection is not automatically enlisted in transactionscope unless you open the connection within the transactionscope. I don't know how your code works, if GetOpenConnection somehow magically opens itself within the transactionscope, but I'd wager that it doesn't – Erik Bergstedt Nov 22 '15 at 09:23
  • @ErikBergstedt, are you saying that the connection **must** be open *only after* we call `.BeginTransaction()` on it? If that was the case, this extension method would promote wrong usage of the transaction. (IMO, it should even throw "cannot open transaction after the connection is already open".) – ANeves Nov 23 '15 at 16:22
  • 4
    Good point to include the transaction as a parameter in `Execute`, as this is required. – Arve Systad Jun 14 '17 at 11:47
58

There are 3 approaches to doing transactions in Dapper.

  1. Simple Transaction
  2. Transaction from Transaction Scope
  3. Using Dapper Transaction (additional nuget package and most favored approach)

You can find out more about these transaction approaches from the official tutorial website here

For reference here's a breakdown of the transaction approaches

1. Simple Transaction

In this example, you create a transaction on an existing db connection, and then pass in the transaction to the Execute method on dapper (which is an optional parameter).

Once you've done all your work, simply commit the transaction.

string sql = "INSERT INTO Customers (CustomerName) Values (@CustomerName);";

using (var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServerW3Schools()))
{
    connection.Open();
    
    using (var transaction = connection.BeginTransaction())
    {
        connection.Execute(sql, new {CustomerName = "Mark"}, transaction: transaction);
        connection.Execute(sql, new {CustomerName = "Sam"}, transaction: transaction);
        connection.Execute(sql, new {CustomerName = "John"}, transaction: transaction);
        
        transaction.Commit();
    }
}

2. Transaction from Transaction Scope

If you'd like to create a transaction scope, you will need to do this before the db connection is created. Once you've created the transaction scope, you can simply perform all your operations and then do a single call to complete the transaction, which will then commit all the commands

using (var transaction = new TransactionScope())
{
    var sql = "INSERT INTO Customers (CustomerName) Values (@CustomerName);";

    using (var connection = My.ConnectionFactory())
    {
        connection.Open();

        connection.Execute(sql, new {CustomerName = "Mark"});
        connection.Execute(sql, new {CustomerName = "Sam"});
        connection.Execute(sql, new {CustomerName = "John"});
    }

    transaction.Complete();
}

3. Using Dapper Transaction

In my opinion, this is the most favorable approach to achieve transaction in code, because it makes the code easy to read and easy to implement. There is an extended implementation of SQL Transaction called Dapper Transaction (which you can find here), which allows you to run the SQL executes off the transactions directly.

string sql = "INSERT INTO Customers (CustomerName) Values (@CustomerName);";

using (var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServerW3Schools()))
{
    connection.Open();
    
    using (var transaction = connection.BeginTransaction())
    {
        transaction.Execute(sql, new {CustomerName = "Mark"});
        transaction.Execute(sql, new {CustomerName = "Sam"});
        transaction.Execute(sql, new {CustomerName = "John"});

        transaction.Commit();
    }
}
Newteq Developer
  • 2,257
  • 1
  • 26
  • 32
  • 4
    This is the correct answer. I'm surprised i had to the last answer to find it. The other answers are *don't use Dapper*, or *don't use Tranaasctions*. This explains how transactions are *supposed* to work with Dapper - and how Dapper *intended* us to use transactions. Bonus: Now that i see it, adding an **Execute** extension method onto the `IDbTransaction` itself was genius on Dapper's part. – Ian Boyd Nov 12 '21 at 13:10
  • 2
    Whenever i need a child method to perform database work, i always passed the `IDbConnection` and the `IDbTransaction`. Normally you'd pass just the `IDbConnection`, but if you're also in a transaction you'd be forced to pass `IDbTransaction` along with it. It wasn't until just now that i realized that `IDbTransaction` contains the `IDbConnection` it came from. So now i **see** what the Microsoft developer 25 years ago was thinking when he designed the ADO.net interfaces - pass just the `IDbTransaction`. – Ian Boyd Nov 12 '21 at 13:14
  • 2
    @IanBoyd I'm glad the answer was able to help I think I was just a little late to the party, but I thought that I'd share some of my understanding and learnings from using Dapper recently – Newteq Developer Nov 12 '21 at 15:31
  • This is the best answer. I'm not sure why there aren't more upvotes. – Brennan Pope Jan 06 '22 at 22:36
  • 1
    Hi @Newteq, 3rd way - Using Dapper Transaction - should there be try/catch and transaction.Rollback()? – Leszek P Apr 22 '22 at 09:43
  • @LeszekP Hi, so you could wrap it in a try catch if you wanted to handle the exception in some way. But the rollback will automatically happen if an exception is thrown, because it won't run the commit, which will at the end of the day not persist anything. The code I provided is just an example of using the different methods of transactions available via dapper, but all of them could have try/catch sections if you want to handle the exception in a certain way. But the rollback doesn't need to be called in the catch, no. – Newteq Developer Apr 22 '22 at 13:30
  • I don't think "dapper-tutorial.net" is related to the DapperLib project, so stating that the 3rd approach is _"most favored"_ is not an officially endorsed opinion. It's still very valid, but opinionated. – Telmo Marques Feb 09 '23 at 11:23
  • 1
    That's fair. I did not consider that. I'll update the answer accordingly – Newteq Developer Feb 09 '23 at 13:34
21

You should be able to use TransactionScope since Dapper runs just ADO.NET commands.

using (var scope = new TransactionScope())
{
   // open connection
   // insert
   // insert
   scope.Complete();
}
Daniel A. White
  • 187,200
  • 47
  • 362
  • 445
14

Considering all your tables are in single database, I disagree with TransactionScope solution suggested in some answers here. Refer this answer.

  1. TransactionScope is generally used for distributed transactions; transaction spanning different databases may be on different system. This needs some configurations on operating system and SQL Server without which this will not work. This is not recommended if all your queries are against single instance of database.
    But, with single database this may be useful when you need to include the code in transaction that is not under your control. With single database, it does not need special configurations either.

  2. connection.BeginTransaction is ADO.NET syntax to implement transaction (in C#, VB.NET etc.) against single database. This does not work across multiple databases.

So, connection.BeginTransaction() is better way to go.

Even the better way to handle the transaction is to implement UnitOfWork as explained in this answer.

Amit Joshi
  • 15,448
  • 21
  • 77
  • 141
  • 8
    One doesn't need multiple databases to benefit from TransactionScope. Of particular utility is that it's ambient. It's great for wrapping code that you don't own or can't modify, in a transaction . For example it can be used to great effect when unit/integration testing code that does database calls where you want to roll back after. Just float a TransactionScope, test the code, and dispose during test cleanup. – Larry Smith Nov 14 '17 at 21:04
  • 4
    @LarrySmith: Agreed; but the question is not about anything of this. OP just says he want to insert in multiple tables in one transaction. Some answers including the accepted one, suggest to use `TransactionScope` which is inefficient for what OP want. I agree that `TransactionScope` is good tool in many cases; but not this. – Amit Joshi Nov 15 '17 at 05:10
6

Daniel's answer worked as expected for me. For completeness, here's a snippet that demonstrates commit and rollback using a transaction scope and 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
  • 6,523
  • 2
  • 59
  • 76
  • 3
    @usr that comes down to personal preference. I prefer to know the first time something went wrong and don't see the log statements as litter. Also, my answer still ads value by demonstrating one way to use transactions with dapper – Sudhanshu Mishra Dec 02 '15 at 19:33
  • @CodeNaked, first, you've got the order wrong there. The catch block would be hit first if there's an exception, then the end of scope for using. Second, look at this answer and the referenced MSDN doc:http://stackoverflow.com/a/5306896/190476 calling dispose a second time isn't harmful, a well designed object ignores the second call. The downvote isn't justified! – Sudhanshu Mishra Aug 23 '16 at 20:18
  • @dotnetguy - I wasn't try to communicate which `Dispose` method is called first or second, just that it's called twice. As to the point that "calling dispose a second time isn't harmful", that's a big assumption. I've learned that the docs and the actual implementations often don't agree. But if you want Microsoft's word for it: https://msdn.microsoft.com/en-us/library/ms182334.aspx?f=255&MSPPError=-2147217396 – CodeNaked Aug 23 '16 at 21:33
  • 4
    So, a code analysis warning is your reason to downvote? That doesn't make the answer wrong or misleading - that's when a downvote is appropriate. Why don't you edit the answer and propose a better solution whilst keeping the functionality? Stack overflow is all about helping and constructive criticism. – Sudhanshu Mishra Aug 23 '16 at 21:40