Tuesday, October 30, 2012

Workflow assembly to cancel waiting workflows - Part 2

In my last blog, I posted a code for a custom workflow assembly that updates the status of waiting workflow to “Cancelled” when a new instance of the workflow is created.
I did not like the fact that I need to pass the name of workflow as a parameter to the assembly. Then this thought across my mind that I may be able to use this workflow for some other scenarios. For e.g What will happen if I deactivate an account which have one or more workflows in a waiting status?
Personally, I would like to cancel all waiting workflows if I deactivate the account. So I change the code.
Now this workflow assembly can be used in 2 different scenarios.
  1. If I pass the name of the workflow to the assembly. It will update the status of waiting instance of the workflow to “Cancelled” .
  2. If I pass nothing, then it will update the status of all the waiting workflows to “Cancelled” for an entity regardless, of the name of the workflow. In the following screen shot, I am leaving the work flow name empty.
Click here to download the workflow.zip. I have uploaded the whole solution.

Workflow assembly to cancel waiting workflows - Part 1

Sometimes we need recursive workflows with waiting condition in the system. For example a workflow that fires on creation and on change of follow up date of the task. This workflow may wait for few days after “follow up” date and create a new task or send an email. If the follow up date is updated, the system will start a new instance of the workflow. It results  in multiple waiting workflows in the system. It will effect the performance of the system.

The ideal situation will be a workflow that can cancel any waiting workflows on start of a new  instance of the workflow.  The original idea is from Ayaz Ahmed’s blog. I took that idea and came up with this  CRM 2011 workflow assembly. It checks for any waiting instances of the workflow and update their status to “Cancelled”. Here is the code.

using System;
using System.Activities;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Xrm.Sdk.Workflow;
using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Sdk.Client;

namespace Workflow
    public class KillWaitingWorkflows: CodeActivity
        protected override void Execute(CodeActivityContext executionContext)

            //Get the tracing service
            //ITracingService tracingService = executionContext.GetExtension<ITracingService>();

            //Get the context
            IWorkflowContext context = executionContext.GetExtension<IWorkflowContext>();
            IOrganizationServiceFactory serviceFactory = executionContext.GetExtension<IOrganizationServiceFactory>();
            IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);

            //get workflow name from the input property
            string sWorkflow = workflowName.Get<string>(executionContext);

            //Get a service context to use linq queries and 
            var ServiceContext = new OrganizationServiceContext(service);

                QueryExpression query = new QueryExpression("asyncoperation");

                //get columns
                ColumnSet cols = new ColumnSet(new string[] { "asyncoperationid", "statecode" });

                //create conditions
                ConditionExpression c1 =
                    new ConditionExpression("name", ConditionOperator.Equal, sWorkflow); // name of the workflow -input property
                ConditionExpression c2 =
                     new ConditionExpression("regardingobjectid", ConditionOperator.Equal, context.PrimaryEntityId);// entity id
                ConditionExpression c3 = 
                     new ConditionExpression("statecode", ConditionOperator.Equal, 1);//statecode of waiting

                //create the filter
                FilterExpression filter = new FilterExpression();
                filter.FilterOperator = LogicalOperator.And;

                query.ColumnSet = cols;

                //get the collection of results
                EntityCollection colResults = service.RetrieveMultiple(query);

                foreach (Entity async in colResults.Entities)
                    Entity e = async;

                    //change the status of the system job
                    e["statecode"] = new OptionSetValue(3); //cancelled

                    //update the object             
            }//end try
            // Catch any service fault exceptions that Microsoft Dynamics CRM throws.
            catch (Exception ex)
                // You can handle an exception here or pass it back to the calling method.
    [Input("Work Flow Name")]
    [Default("workflow name")]
    public InArgument<string> workflowName { get; set; }
  • Compile the workflow assembly and register it using plugin registration tool.
  • If everything goes smoothly, you can see this workflow assembly available in workflow. It will look like the following screens.

  • I am calling the assembly before the waiting condition. Click on the “View properties” link next to assembly name highlighted in yellow to specify the name of the workflow.

  • Activate the workflow and test it.
The problem with this code is that we have to pass “workflow name” as input parameter to the assembly. Then an ideas came to me to use this for my advantage. Wait of my next blog “Workflow assembly to cancel waiting workflows Part 2”.