Open 2Smooth opened 6 years ago
Did you by any chance manually change the next run date on your recurring process record after you initially created it? That can result in duplicate executions being triggered. Also, can you verify you have just a single recurring process record that calls your main workflow?
Finally, can you uncheck the option to automatically delete completed workflow jobs on the "Recurring workflow runner" workflow and see how many process sessions it fires every day?
Yes, I did change the next run date several times in the process of testing it. Is there a way to delete the duplicate executions? Yes, I only have a single Recurring Process calling the main workflow. I already have unchecked to automatically delete workflows. That was the screen shot I posted. These emails go to clients so I stopped sending them out and set up a couple test Contacts with my email address. It was set to send out daily and since I didn’t want to wait that long, I just kept changing the next run date and time. The last run gave me 14 duplicate executions. Sounds like I have been making it worse. My questions are:
Paul
From: Lucas Alexander notifications@github.com Sent: March 25, 2018 5:07 PM To: lucasalexander/AlexanderDevelopment.ProcessRunner AlexanderDevelopment.ProcessRunner@noreply.github.com Cc: Paul Talbot talbot@bizmax.ca; Author author@noreply.github.com Subject: Re: [lucasalexander/AlexanderDevelopment.ProcessRunner] Recurring Process Triggering Work Flow More Than Once (#1)
Did you by any chance manually change the next run date on your recurring process record after you initially created it? That can result in duplicate executions being triggered. Also, can you verify you have just a single recurring process record that calls your main workflow?
Finally, can you uncheck the option to automatically delete completed workflow jobs on the "Recurring workflow runner" workflow and see how many process sessions it fires every day?
— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHubhttps://github.com/lucasalexander/AlexanderDevelopment.ProcessRunner/issues/1#issuecomment-376011349, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AaiqhCq3T-ES1aTkt8KiXYuu0Ek44Kh-ks5tiCMOgaJpZM4S5yvn.
On the completed workflows, can you show the process sessions associated with the recurring process record? That's where you should see duplicate waiting workflows if there are any.
Lucas,
That is straight forward. Thanks for your reply.
Paul
From: Lucas Alexander notifications@github.com Sent: March 26, 2018 10:15 AM To: lucasalexander/AlexanderDevelopment.ProcessRunner AlexanderDevelopment.ProcessRunner@noreply.github.com Cc: Paul Talbot talbot@bizmax.ca; Author author@noreply.github.com Subject: Re: [lucasalexander/AlexanderDevelopment.ProcessRunner] Recurring Process Triggering Work Flow More Than Once (#1)
On the completed workflows, can you show the process sessions associated with the recurring process record? That's where you should see duplicate waiting workflows if there are any.
— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHubhttps://github.com/lucasalexander/AlexanderDevelopment.ProcessRunner/issues/1#issuecomment-376222997, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AaiqhFyvukTyqoPfMDg97tBV_RXNq2NIks5tiRP6gaJpZM4S5yvn.
Lucas, Great project! I have a suggestion that prevents duplicate triggering. As you've noted, your approach will always retrigger the main workflow and queue up a new instance every time you change the underlying Recurring process record.
My solution was to insert a CancelWaitingWorkflowInstances custom workflow step at the top of the workflow that stops any duplicate waiting workflow instances. The custom step ensures you only have one waiting workflow with the indicated name running at any given time against each record. I pasted in a copy in here if you are interested in incorporating it. But it is super useful as a separate Workflow step because you can use it in lots of other places. The only trick to to remember that the workflow name is important.
I borrowed directly from this post with a few tweaks to make allow testing and running outside of workflow: https://community.dynamics.com/crm/b/mscrmshop/archive/2012/10/29/workflow-assembly-to-cancel-waiting-workflows-part-1
Simply insert the custom step at the top of your "Workflow processes runner" workflow:
Thanks again Lucas! Don Schiavone
/ CancelWaitingWorkflowInstances Custom Workflow Step /
using System; using System.Globalization; using System.Activities; using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Workflow; using Microsoft.Xrm.Sdk.Query;
namespace Schiavone.Workflow.Utilities { public class CancelWaitingWorkflowInstances : WorkFlowActivityBase { protected override void Execute(CodeActivityContext context) { if (context == null) { throw new ArgumentNullException("serviceProvider"); }
// Construct the Local plug-in context.
LocalWorkflowContext localcontext = new LocalWorkflowContext(context);
localcontext.Trace(string.Format(CultureInfo.InvariantCulture, "Entered {0}.Execute()", this.DisplayName));
ExecuteCRMWorkFlowActivity(context, localcontext);
}
public override void ExecuteCRMWorkFlowActivity(CodeActivityContext executionContext, LocalWorkflowContext context)
{
//Get the tracing service
ITracingService tracingService = executionContext.GetExtension<ITracingService>();
//Get the context
IOrganizationServiceFactory serviceFactory = executionContext.GetExtension<IOrganizationServiceFactory>();
IOrganizationService service = serviceFactory.CreateOrganizationService(context.WorkflowExecutionContext.UserId);
Guid workflowExecutionContextOperationId = context.WorkflowExecutionContext.PrimaryEntityId;
//get workflow name from the input property
string sWorkflowName = workflowName.Get<string>(executionContext);
Guid relatedEntityId = context.WorkflowExecutionContext.PrimaryEntityId;
bool bCancelThisInstanceToo = cancelThisInstanceToo.Get<bool>(executionContext);
//Get a service context to use linq queries and ...
//var ServiceContext = new OrganizationServiceContext(service);
// var ServiceContext = context.OrganizationService;
tracingService.Trace(string.Format("workflowExecutionContextOperationId={0}, sWorkflowName={1}, relatedEntityId={2}, bCancelThisInstanceToo={3} ",
workflowExecutionContextOperationId, sWorkflowName, relatedEntityId, bCancelThisInstanceToo));
int count = DoCancelWaitingWorkflowInstances(workflowExecutionContextOperationId, sWorkflowName, relatedEntityId, bCancelThisInstanceToo, service);
tracingService.Trace(string.Format("numberOfCancelledWorkflows={0}", numberOfCancelledWorkflows));
numberOfCancelledWorkflows.Set(executionContext, count);
// Throw an exception so we can see the Trace (debugging only)
var shouldForceException = forceException.Get(executionContext);
if (shouldForceException) throw new InvalidPluginExecutionException();
}
public static int DoCancelWaitingWorkflowInstances(Guid workflowExecutionContextOperationId, string sWorkflowName,
Guid relatedEntityId, bool bCancelThisInstanceToo, IOrganizationService service)
{
Guid primaryEntityId = relatedEntityId;
// http://mscrmshop.blogspot.com/2012/10/workflow-assembly-to-cancel-waiting.html
int iCancelledCount = 0;
try
{
QueryExpression query = new QueryExpression("asyncoperation");
// get columns
ColumnSet cols = new ColumnSet(new string[] { "asyncoperationid", "statecode" });
// conditions
// name of the workflow -input property
ConditionExpression c1 = new ConditionExpression("name", ConditionOperator.Equal, sWorkflowName);
// entity id
ConditionExpression c2 = new ConditionExpression("regardingobjectid", ConditionOperator.Equal, primaryEntityId);
// statecode of waiting
ConditionExpression c3 = new ConditionExpression("statecode", ConditionOperator.Equal, 1);
//create the filter
FilterExpression filter = new FilterExpression();
filter.FilterOperator = LogicalOperator.And;
filter.AddCondition(c1);
filter.AddCondition(c2);
filter.AddCondition(c3);
query.ColumnSet = cols;
query.Criteria.AddFilter(filter);
//get the collection of results
EntityCollection colResults = service.RetrieveMultiple(query);
foreach (Entity async in colResults.Entities)
{
Entity e = async;
// If we want to preserve this particular workflow instance, then skip the part where we cancel it
if (!bCancelThisInstanceToo && (e.Id == workflowExecutionContextOperationId))
continue;
//change the status of the system job
e["statecode"] = new OptionSetValue(3); //cancelled
//update the object
service.Update(e);
iCancelledCount++;
}
}//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.
throw;
}
return iCancelledCount;
}
[Input("Workflow Name")]
[Default("workflow name")]
public InArgument<string> workflowName { get; set; }
[Input("Cancel this workflow instance too?")]
[Default("false")]
public InArgument<Boolean> cancelThisInstanceToo { get; set; }
[Input("Force Exception? (To View Tracing)")]
[Default("false")]
public InArgument<Boolean> forceException { get; set; }
[Output("Number of cancelled workflows")]
public OutArgument<int> numberOfCancelledWorkflows { get; set; }
}
}
A Recurring Process I setup to trigger a workflow on a daily basis is often triggering the workflow more than once. Basically, the Recurring Process finds all the contacts whose Next Home Anniversary is today. It triggers a workflow called ABPR: Home Anniversary that adds some other logic, calls a Child Workflow that sends the email and then updates Next Home Anniversary to next year. I have occasionally seen more than one email sent to the same Contact. I updated to your latest version and ran some tests with 2 test Contacts. Looking at the Process Sessions for ABPR: Home Anniversary, in the latest run it was triggered 3 times for each Contact. Attached is the screen shot of the Process Sessions. I will change the logic in ABPR: Home Anniversary to update the Next Home Anniversary prior to calling the child workflow. I hope that will minimize the impact on my clients but doesn't solve the root problem. This is a great solution that is very useful. I hope there is a correction that can be made.