7

How can I perform the equivalent of this? My understanding is that this is impossible with TransactionScopes but I'd like to accomplish the equivalent in some other way:

Business Logic class:

public bool Bar()
{
  try
  {
    using (var tsWork = new TransactionScope())
    {
      ComplicatedDataImportCode(somedata);
      FlagRecordInDatabaseAsImported(); // this is the same record that's modified in the catch
      tsWork.Complete();
      return true;
    }
    catch (DuplicateDataException err)
    {
      // if we got here, the above transaction should have rolled back,
      // so take that same record in the database and update it to "Duplicate".
      FlagSameRecordInDatabaseAsDuplicate(err.Message);
    }

    return false;
}

Now this works fine, until I encapsulate all of this inside of a transaction (perhaps an integration test that I want to rollback after performing asserts).

Simple test to prove my point:

public void CanTest()
{
  // Arrange
  var foo = new Foo();

  using (var ts = new TransactionScope())
  {
    // Act
    var success = foo.Bar();

    // Assert
    if (success)
    {
      Assert.That(SomethingThatTestsThatTheDataWasImported);
    }
    else
    {
      Assert.That(SomethingThatTestsThatTheRecordWasMarkedAsDuplicate);
    }

    // Now that we have been able to perform our Asserts, rollback.

  }
}

Ultimately, the code in Foo.Bar() can be modified to accommodate a solution however, the code in ComplicatedDataImportCode() cannot be modified for this solution, and is consequently what I really need to make sure I properly rollback in the failure scenario.

Again, I understand that TransactionScopes cannot be used to do this, according to the post that I referenced at the beginning of this question. I used TransactionScopes here to indicate what I wanted to do, and am looking for the best alternative way to implement this functionality.

Community
  • 1
  • 1
Jaxidian
  • 13,081
  • 8
  • 83
  • 125

2 Answers2

2

Isn't it so that this must be supported by the DBMS you're using ?

SQL Server doesn't really support nested transactions for instance, however, with SQL Server you can use savepoints.

An article I've written some years ago on my blog.

Frederik Gheysels
  • 56,135
  • 11
  • 101
  • 154
  • 1
    SQL Server supports nested transactions, its just that the first `ROLLBACK` kills all levels. `COMMIT` abides by the nesting though. – Kratz Aug 08 '11 at 19:54
  • Hmm, maybe I'm under the false understanding that SQL Server supports this. I'd like to learn more. Reading your links. +1 for now... – Jaxidian Aug 08 '11 at 20:04
  • @Kratz: Your comment is exactly my problem. I want the `ROLLBACK` to only rollback that specific "inner transaction" rather than the whole thing. – Jaxidian Aug 08 '11 at 20:08
  • I think this is truly the answer to this question. Off to make another question, this time about EF and Savepoints. *shudders* Thanks, Frederik! – Jaxidian Aug 08 '11 at 20:13
  • 1
    It is a common misconception that SQL Server supports nested transactions. It does not. opening multiple transactions and then calling commit does absolutely nothing. you can easily write some test SQL to try this yourself. The only option here to emulate a nested transaction is to use Savepoints as mentioned above. – Dave Black Dec 07 '12 at 17:13
  • I should add that the only thing that matters is when @@TRAN_COUNT reaches zero is the point at which only the outer transaction will be committed. – Dave Black Dec 07 '12 at 17:21
1

If you can get a hold of the active DB connection that ComplicatedDataImportCode is using, you should just have to run a BEGIN TRAN and ROLLBACK TRAN on that connection.

Kratz
  • 4,280
  • 3
  • 32
  • 55
  • This doesn't allow for a partial rollback, though. The rollback dooms the entire transaction and that's the problem. So if I do what you say, then when I perform that rollback, my call to `FlagSameRecordInDatabaseAsDuplicate` won't work because it's within the same transaction when invoked by my CanTest method. – Jaxidian Aug 10 '11 at 15:23