Using a TransactionScopeActivity with a WCF ReceiveActivity
I got an email from a friend last week asking about using a ReceiveActivity and, while receiving, using a TransactionScopeActivity to transitionally save some data in a database. Seems like a common enough scenario right? Well he was having some problems. If everything worked and the transaction succeeded everything was fine and the answer came back. But if an exception occurred and the transaction was aborted be was receiving a real weird error:
System.ServiceModel.FaultException`1[System.ServiceModel.ExceptionDetail]: Workflow service unexpectedly unloaded from memory while executing a ReceiveActivity. Make sure that the the workflow does not contain any blocking activities within a ReceiveActivity. (Fault Detail is equal to An ExceptionDetail, likely created by IncludeExceptionDetailInFaults=true, whose value is:
System.InvalidOperationException: Workflow service unexpectedly unloaded from memory while executing a ReceiveActivity. Make sure that the the workflow does not contain any blocking activities within a ReceiveActivity.).
If we where to believe the message there was a blocking call, something there most certainly was not!
And to make things more confusing, if we removed the TransactionScopeActivity and just let the exception occur it would bubble back to the client just as it was supposed to. So what gives?
Well a lot of people looked at this and in the end we declared this a pretty bad bug. Mind you our words not those from Microsoft. But we did find a workaround. So lets take a look at a repro and how to fix this. My original test workflow looked like this:
I receive a WCF request, start a transaction, determine the return value in a code activity, throw an exception and return. Seems reasonable right? Well I thought so but it produces the error claiming there is a blocking statement. While tracking this Marvin actually noticed that the workflow idle event was raised! Excuse me, a workflow is becoming idle to undo a transaction sounds kind of wrong. And of course when a TransactionScopeActivity is used in a regular workflow, ie non WCF call, there is no Idle event.
A partial solution
So the way to get this to work is create the following workflow:
So instead of creating a TransactionScopeActivity inside of my ReceiveActivity I am doing it the other way round. Sounds kind of weird right? Well I think so but it does do the right thing as it returns the correct fault information to the client and then undoes any transactional work done.
So why does this workaround work in this case?
The ReceiveActivity has its CanCreateInstance property set to true so this is the request that actually creates and starts the workflow. This means that the workflow is created and starts executing from the top. Yes that is right, it starts from the top, not from the ReceiveActivity so any activities before that are also executed. I suppose that potentially opens a can of worms but we will leave that be for the moment. In this case the WCF request is received, this starts the workflow, this in turn starts the transaction, the message is received and processed and the transaction either commits or, like in this case, rolls back.
How about a non creating ReceiveActivity?
Well I am afraid no luck there as this workaround isn't going to work then. The problem is that we start a transaction before we start waiting for the additional WCF calls and the TransactionScopeActivity has a a TimoutDuration. So in all likelihood the transaction timeout will occur before the message is received and this effectively cancels the ReceiveActivity meaning it cannot receive the message.
I think this is a pretty major problem with the way WF and WCF work together. After all transactions a an essential piece of business applications and not being able to use them inside of a WCF request is a deadly sin.
Enjoy with care