34

Which is the proper way of using BeginTransaction() with IDbConnection in Dapper ?

I have created a method in which i have to use BeginTransaction(). Here is the code.

using (IDbConnection cn = DBConnection)
{
    var oTransaction = cn.BeginTransaction();

    try
    {
        // SAVE BASIC CONSULT DETAIL
        var oPara = new DynamicParameters();
        oPara.Add("@PatientID", iPatientID, dbType: DbType.Int32);
        ..........blah......blah............
    }
    catch (Exception ex)
    {
        oTransaction.Rollback();
        return new SaveResponse { Success = false, ResponseString = ex.Message };
    }
}

When i executed above method - i got an exception -

Invalid operation. The connection is closed.

This is because you can't begin a transaction before the connection is opened. So when i add this line: cn.Open();, the error gets resolved. But i have read somewhere that manually opening the connection is bad practice!! Dapper opens a connection only when it needs to.

In Entity framework you can handle a transaction using a TransactionScope.

So my question is what is a good practice to handle transaction without adding the line cn.Open()... in Dapper ? I guess there should be some proper way for this.

Liam
  • 27,717
  • 28
  • 128
  • 190
Krishnraj Rana
  • 6,516
  • 2
  • 29
  • 36

4 Answers4

69

Manually opening a connection is not "bad practice"; dapper works with open or closed connections as a convenience, nothing more. A common gotcha is people having connections that are left open, unused, for too long without ever releasing them to the pool - however, this isn't a problem in most cases, and you can certainly do:

using(var cn = CreateConnection()) {
    cn.Open();
    using(var tran = cn.BeginTransaction()) {
        try {
            // multiple operations involving cn and tran here

            tran.Commit();
        } catch {
            tran.Rollback();
            throw;
        }
    }
}

Note that dapper has an optional parameter to pass in the transaction, for example:

cn.Execute(sql, args, transaction: tran);

I am actually tempted to make extension methods on IDbTransaction that work similarly, since a transaction always exposes .Connection; this would allow:

tran.Execute(sql, args);

But this does not exist today.

TransactionScope is another option, but has different semantics: this could involve the LTM or DTC, depending on ... well, luck, mainly. It is also tempting to create a wrapper around IDbTransaction that doesn't need the try/catch - more like how TransactionScope works; something like (this also does not exist):

using(var cn = CreateConnection())
using(var tran = cn.SimpleTransaction())
{
    tran.Execute(...);
    tran.Execute(...);

    tran.Complete();
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • FFR: this was suggested but rejected as a PR :( https://github.com/StackExchange/dapper-dot-net/pull/429 Marc also participated in the discussion. It was rejected mainly because there is already duplication between sync/async - adding extension methods for transactions would result in all methods being duplicated 4 times. – Vincent Sels Jun 07 '16 at 12:18
  • 6
    @marc-gravell - In the case of rollback, do you have to explicitly call `tran.RollBack`? is the transaction not rolled back automatically on dispose? – MaYaN Sep 04 '16 at 20:58
6

You should not call

cn.Close();

because the using block will try to close too. For the transaction part, yes you can use TransactionScope as well, since it is not an Entity Framework related technique. Have a look at this SO answer: https://stackoverflow.com/a/6874617/566608 It explain how to enlist your connection in the transaction scope. The important aspect is: connection are automatically enlisted in the transaction IIF you open the connection inside the scope.

Community
  • 1
  • 1
Felice Pollano
  • 32,832
  • 9
  • 75
  • 115
  • Yes, you are right, sorry i forgot to remove it. So the link you provided said that you can use TransactionScope with Dapper but you have to write this code - **con.Open()**. So is it a good practice ?? – Krishnraj Rana Jul 09 '14 at 10:20
  • of course you have to open the connection before using it – Felice Pollano Jul 09 '14 at 10:28
4

Take a look at Tim Schreiber solution which is simple yet powerful and implemented using repository pattern and has Dapper Transactions in mind.

The Commit() in the code below shows it.

public class UnitOfWork : IUnitOfWork
{
    private IDbConnection _connection;
    private IDbTransaction _transaction;
    private IBreedRepository _breedRepository;
    private ICatRepository _catRepository;
    private bool _disposed;

    public UnitOfWork(string connectionString)
    {
        _connection = new SqlConnection(connectionString);
        _connection.Open();
        _transaction = _connection.BeginTransaction();
    }

    public IBreedRepository BreedRepository
    {
        get { return _breedRepository ?? (_breedRepository = new BreedRepository(_transaction)); }
    }

    public ICatRepository CatRepository
    {
        get { return _catRepository ?? (_catRepository = new CatRepository(_transaction)); }
    }

    public void Commit()
    {
        try
        {
            _transaction.Commit();
        }
        catch
        {
            _transaction.Rollback();
            throw;
        }
        finally
        {
            _transaction.Dispose();
            _transaction = _connection.BeginTransaction();
            resetRepositories();
        }
    }

    private void resetRepositories()
    {
        _breedRepository = null;
        _catRepository = null;
    }

    public void Dispose()
    {
        dispose(true);
        GC.SuppressFinalize(this);
    }

    private void dispose(bool disposing)
    {
        if (!_disposed)
        {
            if(disposing)
            {
                if (_transaction != null)
                {
                    _transaction.Dispose();
                    _transaction = null;
                }
                if(_connection != null)
                {
                    _connection.Dispose();
                    _connection = null;
                }
            }
            _disposed = true;
        }
    }

    ~UnitOfWork()
    {
        dispose(false);
    }
}
vaheeds
  • 2,594
  • 4
  • 26
  • 36
  • Its nice. Have several questions about the solution. What if dont wanna use transactions lets say for usual select queries? So, as I understood, sql will generate code for transactions after commit() or what? Why do I need do BeginTransaction() if I will not use it in query? Can it affect perfomance for queries where I dont need transactions? Please, dont understand me wrong. I just wanna clarify all things before I start use this in production. – Alex Gurskiy May 28 '17 at 23:23
  • So, I think that better is to add flag (useTransaction = false). In that case, creating instance of unitOfWork we can chose strategy that we need. Am I right? – Alex Gurskiy May 28 '17 at 23:48
  • You don't need to `commit()` when your query is just `SELECT`. So don't worry about performance!. your idea about adding a flag is nice but in fact, it is not necessary. I use it this way and it works like a charm. – vaheeds Jun 03 '17 at 07:03
  • Could you explain why _transaction is disposed in the finally block although _transaction.RollBack() has been called in the catch block? – Efe Zaladin Aug 17 '21 at 20:57
  • 1
    @EfeZaladin `finally` block will run for sure, so disposing of the object is neccessary either way. In case of successful `try`, the `_transaction` should dispose and if something goes wrong, `_transaction` should be rollbacked and in both scenarios, it will be disposed of finally. – vaheeds Aug 18 '21 at 14:54
  • @vaheeds Thanks! It's obvious but I think I saw your answer at a late hour :D I also have an idea and I would like to know it makes sense. Could we open the connection and begin the transaction when a repository's get method is called for the first time, in order not to hog the DB connections when not necessary? – Efe Zaladin Aug 22 '21 at 10:44
  • @EfeZaladin Generally, you don't need transactions for read-only queries like `get`. When you're going to perform some single or batch `write` queries like insert, update or delete, in a row, you will need to begin a `transaction` to have the ability to roll back in case of any error occurs. – vaheeds Aug 28 '21 at 05:32
  • @vaheeds No I mean the getter-setter methods. Eg: private ICatRepository _catRepository – Efe Zaladin Aug 28 '21 at 07:06
-1

There are two intended ways to use transactions with Dapper.

  1. Pass your IDbTranasction to your normal Dapper call.

    Before:

    var affectedRows = connection.Execute(sql, new {CustomerName = "Mark"});
    

    After:

    var affectedRows = connection.Execute(sql, new {CustomerName = "Mark"}, transaction=tx);
    
  2. Use the new .Execute extension method that Dapper adds to IDbTransaction itself:

    tx.Execute(sql, new {CustomerName = "Mark"});
    

Note: the variable tx comes from IDbTransaction tx = connection.BeginTransaction();

This is how you're supposed to use transactions with Dapper; neither of them are TranasctionScope.

Bonus Reading

Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219