5

Consider the following methods.

DoA()
{
  using (TransactionScope scope = new TransactionScope)
  {
    using (SqlConnection connection = new SqlConnection(connectionString))
    {
      connection.Open();
      SqlCommand command = new SqlCommand(query, connection);
      command.ExecuteNonReader();

      DoB();    

      scope.Complete();
    }
  }
}

DoB()
{
  using (TransactionScope scope = new TransactionScope)
  {
    using (SqlConnection connection = new SqlConnection(connectionString))
    {
      connection.Open();
      SqlCommand command = new SqlCommand(query, connection);
      command.ExecuteNonReader();

      DoC();

      scope.Complete();
    }
  }
}

DoC()
{
  using (SqlConnection connection = new SqlConnection(connectionString))
  {
    connection.Open();
    SqlCommand command = new SqlCommand(query, connection);
    command.ExecuteNonReader();
  }
}

If we call DoA(), do the subsequent interactions in DoB() and DoC() run in the context of DoA()'s transaction as it pertains to SQL Server? Does DoC() run in the context of both DoA() and DoB()'s transactions?

(Or am I grossly misunderstanding something?)

svidgen
  • 13,744
  • 4
  • 33
  • 58
  • 1
    You get nested transactions :) – DaveShaw Apr 18 '13 at 21:39
  • There are options that control this. See "Managing transaction flow using TransactionScopeOption" of http://msdn.microsoft.com/en-us/library/ms172152(v=vs.85).aspx – AaronLS Apr 18 '13 at 21:41
  • Note further down remarks about nested transactions must use same isolation level in order to participate in the ambient transaction. – AaronLS Apr 18 '13 at 21:43
  • @DaveShaw I'm struggling to fully understand this, because it's vastly different than my transaction management practices in other languages. Is it accurate to say that `DoC()`'s work runs in `DoB()`'s transaction block? (Which in turn is nested in DoA()'s transaction.) – svidgen Apr 18 '13 at 21:44
  • IMO I think that is accurate way to describe it. It is similar to how nested transactions work in SQL Server. The idea is that if DoC supports transactions, then DoB can do a bunch of things and require that (those things)+DoC() is all-or-nothing. This allows you to build your own methods that are transaction aware, or even DoB might be a call to some other transaction aware document server where you add a document, but also insert a record into SQL Server, and ask that both operations participate in the same transaction. – AaronLS Apr 18 '13 at 21:49
  • The beauty is that DoC can be called without the caller having any knowledge of transactions being used. So the transactional awareness is implicit. Fortunately, they didn't make it OO like ADO.NET, where you have to pass around a transaction object to every method that wants to participate. – AaronLS Apr 18 '13 at 21:53
  • @AaronLS Funny you should mention it. That's precisely what I'm struggling with right now -- inherited code with a mix of TransactionScope blocks and DbTransactions being passed around a DAL ... Need to understand what each type of transaction does to bring some sanity into it. (Many more questions to come, undoubtedly.) – svidgen Apr 18 '13 at 21:57
  • 1
    @svidgen Ah I see, I am not that familiar with DbTransactions, but looks like answer here might address that: http://stackoverflow.com/questions/6277335/how-to-wrap-idbtransactions-in-a-transactionscope – AaronLS Apr 18 '13 at 22:01
  • @AaronLS That Q has similar structures to what I'm seeing in this DAL. I'll take a look -- thanks. You guys have been incredibly helpful in my understanding of `TransactionScope` though. Thanks! – svidgen Apr 18 '13 at 22:11

2 Answers2

5

All code logically will be a single transaction. The nested scopes don't necessarily create a new transaction (unless you use RequiresNew), so it will be a single transaction. Now, each scope must vote to complete the transaction so in your second scope if you remove the Complete, should cause the entire transaction to rollback.

DoC will be part of the transaction as well; the ambient transaction will detect the new connection and be enlisted automatically.

Please read all of the details here which explain the behavior of enrolling in the ambient transaction and the different options Requires, RequiresNew, and Suppress.

Also note that if your connections don't use EXACTLY same connection string, this will automatically promote the entire transaction to a Distributed Transaction. Just something to watch out for.

Andy
  • 8,432
  • 6
  • 38
  • 76
  • +1 Good info. Before I accept, can you just confirm that `DoC()` is executed within the ambient `Transaction` from `DoA()` and/or `DoB()`? (Which is what I *expect* and *think* happens -- but want to be sure.) – svidgen Apr 19 '13 at 17:22
2

Edited per Andy's comments:

It seems as though something like this would occur on the SQL server:

BEGIN TRANSACTION A

  -- do A's work

  -- B does NOT create a new transaction

    -- do B's work

    -- do C's work

COMMIT TRANSACTION A

The following occurs if new TransactionScope(TransactionScopeOption.RequiresNew) is used in DoB().

BEGIN TRANSACTION A

  -- do A's work

  BEING TRANSACTION B

    -- do B's work

    -- do C's work

  COMMIT TRANSACTION B
COMMIT TRANSACTION A
svidgen
  • 13,744
  • 4
  • 33
  • 58
  • That's not what will happen; it will be a single transaction as the scope used in B will join the existing ambient transaction. If B doesn't vote to `Complete` the entire transaction will rollback. – Andy Apr 19 '13 at 17:01
  • @Andy Thanks for the clarification. Is this what will happen if `new TransactionScope(TransactionScopeOptions.RequiresNew)` is used instead? – svidgen Apr 19 '13 at 17:20