3

我正在尝试创建一个简单的 WF4 活动,该活动接受包含 VB.NET 表达式(来自数据库)的字符串,使用工作流当前范围内可用的变量评估该字符串并返回结果。不幸的是,以我尝试过的方式,无论是普通的Activity还是成熟的NativeActivity,我一直在碰壁。

我的第一次尝试是使用一个简单的 Activity,并且我能够创建一个简单的类来评估给定某个对象作为其输入的表达式:

public class Eval<T, TResult> : Activity<TResult>
{
    [RequiredArgument]
    public InArgument<T> Value { get; set; }

    public Eval(string predicate)
    {
        this.Implementation = () => new Assign<TResult>
        {
            Value = new InArgument<TResult>(new VisualBasicValue<TResult>(predicate)),
            To = new ArgumentReference<TResult>("Result")
        };
    }

    public TResult EvalWith(T value)
    {
        return WorkflowInvoker.Invoke(this, new Dictionary<string, object>{ {"Value", value } });
    }
}

这很好,以下表达式的计算结果为 7:

new Eval<int, int>("Value + 2").EvalWith(5)

不幸的是,我不能以我想要的方式使用它,因为表达式字符串是作为构造函数参数而不是作为 给出的InArgument<string>,因此它不能轻易地合并(拖放)到工作流中。我的第二次尝试是尝试使用NativeActivity来摆脱那个讨厌的构造函数参数:

public class NativeEval<T, TResult> : NativeActivity<TResult>
{
    [RequiredArgument] public InArgument<string> ExpressionText { get; set; }
    [RequiredArgument] public InArgument<T> Value { get; set; }

    private Assign Assign { get; set; }
    private VisualBasicValue<TResult> Predicate { get; set; }
    private Variable<TResult> ResultVar { get; set; }

    protected override void CacheMetadata(NativeActivityMetadata metadata)
    {
        base.CacheMetadata(metadata);

        Predicate = new VisualBasicValue<TResult>();
        ResultVar = new Variable<TResult>("ResultVar");
        Assign = new Assign { To = new OutArgument<TResult>(ResultVar), Value = new InArgument<TResult>(Predicate) };

        metadata.AddVariable(ResultVar);
        metadata.AddChild(Assign);
    }

    protected override void Execute(NativeActivityContext context)
    {
        Predicate.ExpressionText = ExpressionText.Get(context);
        context.ScheduleActivity(Assign, new CompletionCallback(AssignComplete));
    }

    private void AssignComplete(NativeActivityContext context, ActivityInstance completedInstance)
    {
        Result.Set(context, ResultVar.Get(context));
    }
}

我尝试NativeEval使用以下命令运行:

WorkflowInvoker.Invoke(new NativeEval<int, int>(), new Dictionary<string, object>
    { { "ExpressionText", "Value + 2" }, { "Value", 5 } });

但得到以下异常:

活动“1:NativeEval”无法访问此变量,因为它是在活动“1:NativeEval”的范围内声明的。一个活动只能访问它自己的实现变量。

所以我改为metadata.AddVariable(ResultVar);metadata.AddImplementationVariable(ResultVar);后来我得到了一个不同的例外:

处理工作流树时遇到以下错误:'VariableReference':引用的变量对象(名称 = 'ResultVar')在此范围内不可见。在此范围内可能存在另一个具有相同名称的位置引用,但它不引用相同的位置。

我尝试.ScheduleFunc()按照此处所述使用来安排VisualBasicValue活动,但它返回的结果始终是null(但奇怪的是没有引发异常)。

我难住了。WF4 的元编程模型似乎比 的元编程模型困难得多System.Linq.Expressions,尽管它很困难并且经常令人困惑(就像元编程通常一样),但至少我能够理解它。我想这是因为它需要表示一个可持久的、可恢复的、异步的、可重定位的程序,而不仅仅是一个普通的旧程序,这增加了复杂性。


编辑:由于我不认为我遇到的问题是由于我正在尝试评估未硬编码的表达式,因此可以对其进行以下更改NativeActivity,使其具有静态表达式:

代替

Predicate = new VisualBasicValue<TResult>();

Predicate = new VisualBasicValue<TResult>("ExpressionText.Length");

并删除线

Predicate.ExpressionText = ExpressionText.Get(context);

现在即使使用这些行表达式是静态的,我仍然会遇到相同的错误。


EDIT2这篇文章解决了我遇到的异常。我必须将变量和子活动都更改为“实现”,所以这是:

metadata.AddVariable(ResultVar);
metadata.AddChild(Assign);

改为:

metadata.AddImplementationVariable(ResultVar);
metadata.AddImplementationChild(Assign);

并导致所有异常消失。不幸的是,它显示以下行完全没有任何作用:

Predicate.ExpressionText = ExpressionText.Get(context);

在运行时更改ExpressionTexta 的属性VisualBasicValue无效。对 ILSpy 的快速检查揭示了原因 - 表达式文本仅在CacheMetadata()被调用时被评估并转换为表达式树,此时表达式尚不知道,这就是为什么我使用无参数构造函数将表达式初始化并具体化为无操作。我什至尝试保存NativeActivityMetadata我在自己的 CacheMetadata 覆盖方法中获得的对象,然后使用反射来强制调用VisualBasicValue's CacheMetadata(),但这最终引发了一个不同的神秘异常(“Ambiguous match found.”类型为 AmbiguousMatchException)。

在这一点上,似乎不可能将动态表达式完全集成到工作流中,将所有范围内的变量都暴露给它。我想我会在课堂上使用我Eval的课堂上使用的方法NativeEval

4

3 回答 3

3

我最终使用了以下活动。它无法访问工作流的变量,而是接受单个参数“值”,该参数可由动态表达式中的相同名称使用。除此之外它工作得很好。

public class Evaluate<TIn, TOut> : NativeActivity<TOut>
{
    [RequiredArgument]
    public InArgument<string> ExpressionText { get; set; }

    [RequiredArgument]
    public InArgument<TIn> Value { get; set; }

    protected override void Execute(NativeActivityContext context)
    {
        var result = new ExpressionEvaluator<TIn, TOut>(ExpressionText.Get(context)).EvalWith(Value.Get(context));
        Result.Set(context, result);
    }
}

public class ExpressionEvaluator<TIn, TOut> : Activity<TOut>
{
    [RequiredArgument]
    public InArgument<TIn> Value { get; set; }

    public ExpressionEvaluator(string predicate)
    {
        VisualBasic.SetSettingsForImplementation(this, VbSettings);

        Implementation = () => new Assign<TOut>
        {
            Value = new InArgument<TOut>(new VisualBasicValue<TOut>(predicate)),
            To = new ArgumentReference<TOut>("Result")
        };
    }

    public TOut EvalWith(TIn value)
    {
        return WorkflowInvoker.Invoke(this, new Dictionary<string, object> { { "Value", value } });
    }

    private static readonly VisualBasicSettings VbSettings;

    static ExpressionEvaluator()
    {
        VbSettings = new VisualBasicSettings();
        AddImports(typeof(TIn), VbSettings.ImportReferences);
        AddImports(typeof(TOut), VbSettings.ImportReferences);
    }

    private static void AddImports(Type type, ISet<VisualBasicImportReference> imports)
    {
        if (type.IsPrimitive || type == typeof(void) || type.Namespace == "System")
            return;

        var wasAdded = imports.Add(new VisualBasicImportReference { Assembly = type.Assembly.GetName().Name, Import = type.Namespace });

        if (!wasAdded)
            return;

        if (type.BaseType != null)
            AddImports(type.BaseType, imports); 

        foreach (var interfaceType in type.GetInterfaces())
            AddImports(interfaceType, imports);

        foreach (var property in type.GetProperties())
            AddImports(property.PropertyType, imports);

        foreach (var method in type.GetMethods())
        {
            AddImports(method.ReturnType, imports);

            foreach (var parameter in method.GetParameters())
                AddImports(parameter.ParameterType, imports);

            if (method.IsGenericMethod)
            {
                foreach (var genericArgument in method.GetGenericArguments())
                    AddImports(genericArgument, imports);
            }
        }

        if (type.IsGenericType)
        {
            foreach (var genericArgument in type.GetGenericArguments())
                AddImports(genericArgument, imports);
        }
    }
}

编辑:更新类以包括完整的程序集和命名空间导入,以免您收到可怕(且无用)的错误消息:

未声明“价值”。由于其保护级别,它可能无法访问。

此外,将 ExpressionEvaluator 类移到外部并将其公开,因此您可以在 WF 之外使用它,如下所示:

new ExpressionEvaluator<int, double>("Value * Math.PI").EvalWith(2);

哪个会返回:

6.28318530717959

于 2012-05-29T12:46:14.133 回答
0

我建议为此使用不同的框架。一种好方法是使用 nCalc。http://ncalc.codeplex.com/

它可以解析任何表达式并评估结果,包括静态或动态参数和自定义函数。

我们使用它在运行时评估不同类型的表达式。

于 2012-04-23T18:19:31.070 回答
0

如果您的“谓词”是众所周知的字符串,并且不需要是在运行时评估的表达式,那么您当然可以这样做,丢弃InArgument并避免构造函数:

public class Eval<T, TResult> : Activity<TResult>
{
    public string Expression { get; set; }

    [RequiredArgument]
    public InArgument<T> Value { get; set; }

    protected override Func<Activity> Implementation
    {
        get
        {
            if (string.IsNullOrEmpty(Expression))
            {
                return base.Implementation;
            }

            return () => new Assign<TResult>
            {
                Value = new InArgument<TResult>(new VisualBasicValue<TResult>(Expression)),
                To = new ArgumentReference<TResult>("Result")
            };
        }
        set
        {
            throw new NotSupportedException();
        }
    }
}

并这样称呼它:

var activity = new Eval<int, int>() { Expression = "Value + 2" };

var inputArgs = new Dictionary<string, object>()
{
    { "Value", 5 }
};

Console.WriteLine("RESULT: " + WorkflowInvoker.Invoke<int>(activity, inputArgs));

编辑:检查即使Predicate.ExpressionText没有评论,它也没有任何效果:

public class NativeEval<T, TResult> : NativeActivity<TResult>
{
    [RequiredArgument]
    public InArgument<string> ExpressionText { get; set; }
    [RequiredArgument]
    public InArgument<T> Value { get; set; }

    private Assign Assign { get; set; }
    private VisualBasicValue<TResult> Predicate { get; set; }
    private Variable<TResult> ResultVar { get; set; }

    protected override void CacheMetadata(NativeActivityMetadata metadata)
    {
        base.CacheMetadata(metadata);

        Predicate = new VisualBasicValue<TResult>("ExpressionText.Length");
        ResultVar = new Variable<TResult>("ResultVar");
        Assign = new Assign { To = new OutArgument<TResult>(ResultVar), Value = new InArgument<TResult>(Predicate) };

        metadata.AddImplementationVariable(ResultVar);
        metadata.AddImplementationChild(Assign);
    }

    protected override void Execute(NativeActivityContext context)
    {
        // this line, commented or not, is the same!
        Predicate.ExpressionText = ExpressionText.Get(context);
        context.ScheduleActivity(Assign, new CompletionCallback(AssignComplete));
    }

    private void AssignComplete(NativeActivityContext context, ActivityInstance completedInstance)
    {
        // the result will always be the ExpressionText.Length 
        Result.Set(context, ResultVar.Get(context));
    }
}

当您Execute()更改方法时,子实现无效。执行模式已打开,无法更改子树。

于 2012-04-23T23:30:04.323 回答