Reputation:
I'm trying to work with some ambient transaction scopes (thanks, entity-framework), 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
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
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