diff --git a/src/WorkflowCore.DSL/Services/DefinitionLoader.cs b/src/WorkflowCore.DSL/Services/DefinitionLoader.cs index 5d6595550..29465dbf9 100644 --- a/src/WorkflowCore.DSL/Services/DefinitionLoader.cs +++ b/src/WorkflowCore.DSL/Services/DefinitionLoader.cs @@ -318,12 +318,14 @@ private void AttachDirectlyOutput(KeyValuePair output, WorkflowS propertyInfo = dataType.GetProperty("Item"); targetProperty = Expression.Property(dataParameter, propertyInfo, Expression.Constant(output.Key)); + var compiledSourceExpr = sourceExpr.Compile(); + Action acn = (pStep, pData) => { object resolvedValue; try { - resolvedValue = sourceExpr.Compile().DynamicInvoke(pStep); + resolvedValue = compiledSourceExpr.DynamicInvoke(pStep); } catch (TargetInvocationException ex) { @@ -377,13 +379,16 @@ private void AttachNestedOutput(KeyValuePair output, WorkflowSte } propertyInfo = ((PropertyInfo)memberExpression.Member).PropertyType.GetProperty("Item"); + var targetExpr = Expression.Lambda(memberExpression, dataParameter); + var compiledTargetExpr = targetExpr.Compile(); + var compiledSourceExpr = sourceExpr.Compile(); + Action acn = (pStep, pData) => { - var targetExpr = Expression.Lambda(memberExpression, dataParameter); object data; try { - data = targetExpr.Compile().DynamicInvoke(pData); + data = compiledTargetExpr.DynamicInvoke(pData); } catch (TargetInvocationException ex) { @@ -392,7 +397,7 @@ private void AttachNestedOutput(KeyValuePair output, WorkflowSte object resolvedValue; try { - resolvedValue = sourceExpr.Compile().DynamicInvoke(pStep); + resolvedValue = compiledSourceExpr.DynamicInvoke(pStep); } catch (TargetInvocationException ex) { @@ -470,12 +475,14 @@ private static Action BuildScalarInput throw new WorkflowDefinitionLoadException($"Error parsing input expression '{expr}' for property '{input.Key}': {ex.Message}", ex); } + var compiledExpr = sourceExpr.Compile(); + void acn(IStepBody pStep, object pData, IStepExecutionContext pContext) { object resolvedValue; try { - resolvedValue = sourceExpr.Compile().DynamicInvoke(pData, pContext, Environment.GetEnvironmentVariables()); + resolvedValue = compiledExpr.DynamicInvoke(pData, pContext, Environment.GetEnvironmentVariables()); } catch (TargetInvocationException ex) { @@ -505,6 +512,40 @@ void acn(IStepBody pStep, object pData, IStepExecutionContext pContext) private static Action BuildObjectInputAction(KeyValuePair input, ParameterExpression dataParameter, ParameterExpression contextParameter, ParameterExpression environmentVarsParameter, PropertyInfo stepProperty) { + // Pre-compile all @-prefixed property expressions at definition load time + var compiledExpressions = new Dictionary(); + var templateObj = JObject.FromObject(input.Value); + var scanStack = new Stack(); + scanStack.Push(templateObj); + + while (scanStack.Count > 0) + { + var subobj = scanStack.Pop(); + foreach (var prop in subobj.Properties()) + { + if (prop.Name.StartsWith("@")) + { + var exprText = prop.Value.ToString(); + if (!compiledExpressions.ContainsKey(exprText)) + { + LambdaExpression sourceExpr; + try + { + sourceExpr = DynamicExpressionParser.ParseLambda(ParsingConfig, false, new[] { dataParameter, contextParameter, environmentVarsParameter }, typeof(object), TransformExpression(exprText)); + } + catch (Exception ex) when (ex is System.Linq.Dynamic.Core.Exceptions.ParseException || ex is InvalidOperationException) + { + throw new WorkflowDefinitionLoadException($"Error parsing input expression '{exprText}': {ex.Message}", ex); + } + compiledExpressions[exprText] = sourceExpr.Compile(); + } + } + } + + foreach (var child in subobj.Children()) + scanStack.Push(child); + } + void acn(IStepBody pStep, object pData, IStepExecutionContext pContext) { var stack = new Stack(); @@ -518,11 +559,11 @@ void acn(IStepBody pStep, object pData, IStepExecutionContext pContext) { if (prop.Name.StartsWith("@")) { - var sourceExpr = DynamicExpressionParser.ParseLambda(ParsingConfig, false, new[] { dataParameter, contextParameter, environmentVarsParameter }, typeof(object), TransformExpression(prop.Value.ToString())); + var exprText = prop.Value.ToString(); object resolvedValue; try { - resolvedValue = sourceExpr.Compile().DynamicInvoke(pData, pContext, Environment.GetEnvironmentVariables()); + resolvedValue = compiledExpressions[exprText].DynamicInvoke(pData, pContext, Environment.GetEnvironmentVariables()); } catch (TargetInvocationException ex) { diff --git a/src/WorkflowCore/Models/MemberMapParameter.cs b/src/WorkflowCore/Models/MemberMapParameter.cs index e5273986d..13ae0f38e 100644 --- a/src/WorkflowCore/Models/MemberMapParameter.cs +++ b/src/WorkflowCore/Models/MemberMapParameter.cs @@ -9,6 +9,7 @@ public class MemberMapParameter : IStepParameter { private readonly LambdaExpression _source; private readonly LambdaExpression _target; + private readonly Delegate _compiledSource; public MemberMapParameter(LambdaExpression source, LambdaExpression target) { @@ -17,19 +18,20 @@ public MemberMapParameter(LambdaExpression source, LambdaExpression target) _source = source; _target = target; + _compiledSource = source.Compile(); } - private void Assign(object sourceObject, LambdaExpression sourceExpr, object targetObject, LambdaExpression targetExpr, IStepExecutionContext context) + private void Assign(object sourceObject, object targetObject, IStepExecutionContext context) { object resolvedValue = null; - switch (sourceExpr.Parameters.Count) + switch (_source.Parameters.Count) { case 1: - resolvedValue = sourceExpr.Compile().DynamicInvoke(sourceObject); + resolvedValue = _compiledSource.DynamicInvoke(sourceObject); break; case 2: - resolvedValue = sourceExpr.Compile().DynamicInvoke(sourceObject, context); + resolvedValue = _compiledSource.DynamicInvoke(sourceObject, context); break; default: throw new ArgumentException(); @@ -37,24 +39,24 @@ private void Assign(object sourceObject, LambdaExpression sourceExpr, object tar if (resolvedValue == null) { - var defaultAssign = Expression.Lambda(Expression.Assign(targetExpr.Body, Expression.Default(targetExpr.ReturnType)), targetExpr.Parameters.Single()); + var defaultAssign = Expression.Lambda(Expression.Assign(_target.Body, Expression.Default(_target.ReturnType)), _target.Parameters.Single()); defaultAssign.Compile().DynamicInvoke(targetObject); return; } - var valueExpr = Expression.Convert(Expression.Constant(resolvedValue), targetExpr.ReturnType); - var assign = Expression.Lambda(Expression.Assign(targetExpr.Body, valueExpr), targetExpr.Parameters.Single()); + var valueExpr = Expression.Convert(Expression.Constant(resolvedValue), _target.ReturnType); + var assign = Expression.Lambda(Expression.Assign(_target.Body, valueExpr), _target.Parameters.Single()); assign.Compile().DynamicInvoke(targetObject); } public void AssignInput(object data, IStepBody body, IStepExecutionContext context) { - Assign(data, _source, body, _target, context); + Assign(data, body, context); } public void AssignOutput(object data, IStepBody body, IStepExecutionContext context) { - Assign(body, _source, data, _target, context); + Assign(body, data, context); } } } diff --git a/test/Directory.Build.props b/test/Directory.Build.props index cd70cf018..1947c22b5 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -1,6 +1,6 @@ - net6.0;net8.0 + net6.0;net8.0;net10.0 latest false diff --git a/test/WorkflowCore.IntegrationTests/WorkflowCore.IntegrationTests.csproj b/test/WorkflowCore.IntegrationTests/WorkflowCore.IntegrationTests.csproj index c92182936..996a9831e 100644 --- a/test/WorkflowCore.IntegrationTests/WorkflowCore.IntegrationTests.csproj +++ b/test/WorkflowCore.IntegrationTests/WorkflowCore.IntegrationTests.csproj @@ -7,7 +7,7 @@ false false false - net6.0 + net6.0;net8.0;net10.0 diff --git a/test/WorkflowCore.UnitTests/WorkflowCore.UnitTests.csproj b/test/WorkflowCore.UnitTests/WorkflowCore.UnitTests.csproj index ff8f2e19e..4822cced6 100644 --- a/test/WorkflowCore.UnitTests/WorkflowCore.UnitTests.csproj +++ b/test/WorkflowCore.UnitTests/WorkflowCore.UnitTests.csproj @@ -7,7 +7,7 @@ false false false - net6.0 + net6.0;net8.0;net10.0