danielgerlag / workflow-core

Lightweight workflow engine for .NET Standard
MIT License
5.39k stars 1.2k forks source link

Add 'contextParameter' to 'CancelCondition' #563

Open RuneMidgart opened 4 years ago

RuneMidgart commented 4 years ago

Hi Daniel, I want to use 'WaitFor' in a 'ForEach' loop, but now I have a problem in 'CancelCondition'. I want every context in the loop has its own 'CancelCondition'. I think adding 'contextParameter' to 'CancelCondition' might solve this problem. Just like this dsl json:

{
    "Id": "Parallel",
    "StepType": "WorkflowCore.Primitives.ForEach, WorkflowCore",
    "Inputs": {
        "Collection": "data[\"List\"]"
    },
    "Do": [
        [{
            "Id": "Parallel-1",
            "StepType": "MyApp.Steps.MyWaitForStep, MyApp",
            "CancelCondition": "DateTime.Now>=context.Item.ExpireTime",
            "Inputs": {
                "Value": "context.Item"
            }
        }]
    ]
}

Now, 'CancelCondition' only support dataParameter.

public IStepBuilder<TData, WaitFor> WaitFor(string eventName, Expression<Func<TData, string>> eventKey, Expression<Func<TData, DateTime>> effectiveDate = null, Expression<Func<TData, bool>> cancelCondition = null)

And this in DefinitionLoader.

if (!string.IsNullOrEmpty(nextStep.CancelCondition))
{
    var cancelExprType = typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(dataType, typeof(bool)));
    var dataParameter = Expression.Parameter(dataType, "data");
    var cancelExpr = DynamicExpressionParser.ParseLambda(new[] { dataParameter }, typeof(bool), nextStep.CancelCondition);
    targetStep.CancelCondition = cancelExpr;
}

Sorry for my pool English. Thx!

RuneMidgart commented 4 years ago

I tried to change the source code about the 'CancelCondition'. And it works. But another problem came out. The 'CancellationProcessor' is going to cancel all the steps that have the same stepId, this is not what I expected.

if (cancel)
{
    var toCancel = workflow.ExecutionPointers.Where(x => x.StepId == step.Id && x.Status != PointerStatus.Complete && x.Status != PointerStatus.Cancelled).ToList();

    foreach (var ptr in toCancel)
    {
        if (step.ProceedOnCancel)
        {
            _executionResultProcessor.ProcessExecutionResult(workflow, workflowDef, ptr, step, ExecutionResult.Next(), executionResult);
        }

        ptr.EndTime = DateTime.Now.ToUniversalTime();
        ptr.Active = false;
        ptr.Status = PointerStatus.Cancelled;

        foreach (var descendent in workflow.ExecutionPointers.FindByScope(ptr.Id).Where(x => x.Status != PointerStatus.Complete && x.Status != PointerStatus.Cancelled))
        {
            descendent.EndTime = DateTime.Now.ToUniversalTime();
            descendent.Active = false;
            descendent.Status = PointerStatus.Cancelled;
        }
    }
}

So, I think maybe adding an 'expireTime' parameter to 'WaitForEvent' might be the best.