WF4 Workflow Services–Can you use the Same Operation more than once?

Thanks to Jean-Sebastien who contacted me with this question.

Imagine you have the following service contract

    1: public interface IBaseMyServiceContract
    2: {
    3:     [OperationContract]
    4:     string CallAMethod(string correlationHandle, string data);
    5: }

Now suppose you want to create a workflow service which implements this contract using the technique I showed in my blog post How to make a WorkflowService implement a contractSNAGHTML1d46457

But to make it interesting suppose you want to use CallAMethod twice.  Once to create the workflow and once to continue it.  Can it be done?  No.  The answer is no, but the interesting question is why not? 

Take this workflow service definition,

The First Receive waits for a service operation named CallAMethod.

It initializes a correlation handle and then sends back a response.

The second receive also waits for a service operation named CallAMethod.

If you try to invoke CallAMethod twice on this workflow what do you suppose will happen?

An error processing the current work item has caused the workflow to abort.  See the inner exception for details. InnerException Message: The execution of an InstancePersistenceCommand was interrupted by a key collision. The instance key with value 'a3a7d0a1-bf67-337e-dac6-e47a9cf86d59' could not be associated to instance 'd5e0789a-f3b5-4f26-bc58-cfd94660b9c2' because it is already associated to a different instance, 'ba4ce9e8-2d0e-4a73-8f13-3ea911cbe784

Why would this happen?

After Jean-Sebastian sent me this test case I decided to investigate it further.  He had a console application to invoke the service but I decided to use Microsoft.Activities.UnitTesting to test the service because the tracking data often helps you to understand exactly what is going on.

So I created a unit test and here is what I found

    1: [TestMethod]
    2: [DeploymentItem(@"DeclarativeServiceLibrary1\Service1.xamlx")]
    3: public void ShouldInvokeService1()
    4: {
    5:     // Note: Host using named pipes so you don't have to worry about HTTP ACLs
    6:     // or running VS as administrator
    7:     var serviceAddress = new EndpointAddress("net.pipe://localhost/service/Service1.xamlx");
    8:     var netNamedPipeBinding = new NetNamedPipeBinding();
    9:     var proxy = ChannelFactory<IBaseMyServiceContract>.CreateChannel(netNamedPipeBinding, serviceAddress);
   10:  
   11:     using (var host = WorkflowServiceTestHost.Open("Service1.xamlx", serviceAddress.Uri))
   12:     {
   13:         try
   14:         {
   15:             var name = this.TestContext.TestName;
   16:  
   17:             var result = proxy.CallAMethod(name, name);
   18:             var result2 = proxy.CallAMethod(name, name);
   19:             ((ICommunicationObject)proxy).Close();
   20:  
   21:             Assert.AreEqual(result, "First Receive");
   22:             Assert.AreEqual(result2, "Second Receive");
   23:         }
   24:         catch
   25:         {
   26:             ((ICommunicationObject)proxy).Abort();
   27:             throw;
   28:         }
   29:         finally
   30:         {
   31:             host.Tracking.Trace();
   32:         }
   33:     }
   34: }

The second CallAMethod is never invoked.  The reason we get a key collision is that the first CallAMethod is called twice.  The first time the workflow gets persisted with the key.  The second time when it tries to create a new workflow with the same key, the instance persistence command aborts because of the key collision.

When I look at the tracking data I see the reason why the second CallAMethod is never invoked.

Bookmark <CallAMethod|IBaseMyServiceContract> resumed with payload <System.ServiceModel.Activities.WorkflowOperationContext> owner <InternalReceiveMessage>

When the receive activity is scheduled, it actually invokes another activity named InternalReceiveMessage which creates a bookmark using the form <Operation>|<Contract>

When WorkflowServiceHost loaded this service it examined the activity tree and then started listening for calls to CallAMethod.  Because the CanCreateInstance property is set to true, every time WorkflowServiceHost gets a message with this contract and operation name it tries to create a new workflow instance which will result in the failed persistence operation.

What if CanCreateInstance were not set to true?  What if you tried using the same operation/contract more than once when it was not the creating receive will that work?  Yes that will work.

Any operation / contract that has CanCreateInstance set to true can be used only once in your workflow service.

Sample code is attached.

WFServiceOpMoreThanOnce.zip