我正在尝试创建一个简单的 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);
在运行时更改ExpressionText
a 的属性VisualBasicValue
无效。对 ILSpy 的快速检查揭示了原因 - 表达式文本仅在CacheMetadata()
被调用时被评估并转换为表达式树,此时表达式尚不知道,这就是为什么我使用无参数构造函数将表达式初始化并具体化为无操作。我什至尝试保存NativeActivityMetadata
我在自己的 CacheMetadata 覆盖方法中获得的对象,然后使用反射来强制调用VisualBasicValue
's CacheMetadata()
,但这最终引发了一个不同的神秘异常(“Ambiguous match found.”类型为 AmbiguousMatchException)。
在这一点上,似乎不可能将动态表达式完全集成到工作流中,将所有范围内的变量都暴露给它。我想我会在课堂上使用我Eval
的课堂上使用的方法NativeEval
。