user1228
user1228

Reputation:

Why -- HOW -- are transactions processed after disposal?

I'm trying to work with some ambient transaction scopes (thanks, ), which I haven't really done before, and I'm seeing some ... odd behavior that I'm trying to understand.

I'm trying to enlist in the current transaction scope and do some work after it completes successfully. My enlistment participant implements IDisposable due to some resources it holds. I've got a simple example that exhibits the strange behavior.

For this class,

class WtfTransactionScope : IDisposable, IEnlistmentNotification
{
    public WtfTransactionScope()
    {
        if(Transaction.Current == null)
            return;
        Transaction.Current.EnlistVolatile(this, EnlistmentOptions.None);
    }
    void IEnlistmentNotification.Commit(Enlistment enlistment)
    {
        enlistment.Done();
        Console.WriteLine("Committed");
    }

    void IEnlistmentNotification.InDoubt(Enlistment enlistment)
    {
        enlistment.Done();
        Console.WriteLine("InDoubt");
    }

    void IEnlistmentNotification.Prepare(
        PreparingEnlistment preparingEnlistment)
    {
        Console.WriteLine("Prepare called");
        preparingEnlistment.Prepared();
        Console.WriteLine("Prepare completed");
    }

    void IEnlistmentNotification.Rollback(Enlistment enlistment)
    {
        enlistment.Done();
        Console.WriteLine("Rolled back");
    }

    public void Dispose()
    {
        Console.WriteLine("Disposed");
    }
}

when used as illustrated here

using(var scope = new TransactionScope())
using(new WtfTransactionScope())
{
    scope.Complete();
}

the console output demonstrates the wtf-ness:

Disposed
Prepare called
Committed
Prepare completed

wut.

I'm getting disposed before the transaction completes. This... kind of negates the benefits of hanging with the transaction scope. I was hoping that I'd be informed once the transaction completes successfully (or not) so I could do some work. I was unfortunately assuming that this would happen after scope.Complete() and before I get disposed as we move out of the using scope. This apparently is not the case.

Of course, I could hack it. But I've got other issues which essentially prevent me from doing this. I'll have to scrap and do something else in this eventuality.

Am I doing something wrong here? Or is this expected behavior? Can something be done differently to prevent this from happening??

Upvotes: 10

Views: 718

Answers (1)

Hans Passant
Hans Passant

Reputation: 941455

This is self-inflicted pain. You violate a very basic rule for IDisposable, an object should only ever be disposed when it is no longer in use anywhere else. It is in use when your using statement calls Dispose(), you handed a reference to your object in the WtfTransactionScope constructor. You cannot dispose it until it is done with it, that necessarily means you have to dispose it after the using statement for the TransactionScope completes and the transaction got committed/rolled-back.

I'll let you fret about making it pretty, but an obvious way is:

using(var wtf = new WtfTransactionScope())
using(var scope = new TransactionScope())
{
    wtf.Initialize();
    scope.Complete();
}

The Prepare completed is merely an unhelpful debug statement. Just delete it.

Upvotes: 14

Related Questions