WF4:我如何评价在运行时才知道的当然pression? [英] WF4: How do I evaluate an expression only known at runtime?

查看:189
本文介绍了WF4:我如何评价在运行时才知道的当然pression?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想创建一个接受一个包含VB.NET EX pression(从说,数据库)的字符串的简单WF4活动,评估利用的工作流和收益的当前范围内可用的变量字符串结果。不幸的是,该方法我已经试过了,无论是对活动或一个完整的 NativeActivity的,我一直打了一堵墙。

I am trying to create a simple WF4 activity that accepts a string that contains a VB.NET expression (from say the database), evaluates that string using the variables available in the current scope of the workflow and returns the result. Unfortunately, with the ways I've tried it, whether it be with a plain on Activity or a full-fledged NativeActivity, I keep hitting a wall.

我的第一次尝试用一个简单的活动,我也能够做出这样的评估给予一定的对象作为其输入的前pression一个简单的类:

My first attempt was with a simple Activity, and I was able to make a simple class that evaluates an expression given some object as its input:

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 } });
    }
}

这炒菜很好,下面EX pression计算结果为7:

This woks nicely, and the following expression evaluates to 7:

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

不幸的是,我不能使用它,我想既然EX pression字符串作为一个构造函数参数,而不是作为道路的 InArgument&LT;字符串&GT; ,因此它不能容易地结合(拖放)到工作流。我的第二次​​尝试是试图用 NativeActivity的来摆脱那个讨厌的构造函数参数:

Unfortunately, I can't use it the way I want since the expression string is given as a constructor argument instead of as an InArgument<string>, so it can't be easily incorporated (dragged and dropped) into a workflow. My second attempt was to try and use NativeActivity to get rid of that pesky constructor parameter:

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 } });

但有以下异常:

But got the following exception:

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

Activity '1: NativeEval' cannot access this variable because it is declared at the scope of activity '1: NativeEval'. An activity can only access its own implementation variables.

于是我改变了 metadata.AddVariable(ResultVar); metadata.AddImplementationVariable(ResultVar); 后来我得到了一个不同的异常:

So I changed metadata.AddVariable(ResultVar); to metadata.AddImplementationVariable(ResultVar); but then I got a different exception:

下面的错误是在处理工作流程树遇到的:   VariableReference:引用的变量对象(名称='ResultVar')是不是在这个范围内可见。可能存在具有相同名称中可见,在此范围内的其他位置的参考,但它不参考相同的位置。

The following errors were encountered while processing the workflow tree: 'VariableReference': The referenced Variable object (Name = 'ResultVar') is not visible at this scope. There may be another location reference with the same name that is visible at this scope, but it does not reference the same location.

我试图用 .ScheduleFunc()如<一个href="https://msmvps.com/blogs/theproblemsolver/archive/2011/04/05/scheduling-child-activities-with-input-parameters.aspx"相对=nofollow>这里安排 VisualBasicValue 的活动,但它返回的结果总是(但奇怪的是没有异常被抛出)。

I tried using .ScheduleFunc() as described here to schedule a VisualBasicValue activity, but the result it returned was always null (but oddly enough no exceptions were thrown).

我难倒。 WF4的元编程模式似乎变得更加困难比 System.Linq.Ex pressions 的元编程模型,它虽然是困难的,往往困扰(如:元编程通常是这样),至少我能环绕它我的头。我想这是因为它有添加需要重新present一个持久化的,可恢复的,异步,可重新定位的程序,而不仅仅是一个普通的旧程序的复杂性。

I'm stumped. The metaprogramming model of WF4 seems much more difficult than the metaprogramming model of System.Linq.Expressions, which albeit difficult and often perplexing (like metaprogramming usually is), at least I was able to wrap my head around it. I guess it's because it has the added complexity of needing to represent a persistable, resumable, asynchronous, relocatable program, rather than just a plain old program.

编辑:因为我不认为我遇到的问题是由事实,我想评价一个前pression一点都不难codeD,以下变更,可到了 NativeActivity的是因为它有一个静态EX pression:

Since I don't think the issue I'm experiencing is caused by the fact that I'm trying to evaluate an expression that isn't hardcoded, the following alteration can be made to the NativeActivity that cause it to have a static expression:

替换

Predicate = new VisualBasicValue<TResult>();

通过

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

和删除行

Predicate.ExpressionText = ExpressionText.Get(context);

现在,即使与线前pression是静态的,我仍然得到同样的错误。

Now even though with those lines the expression is static, I'm still getting the same errors.

EDIT2 :<一href="http://blogs.msdn.com/b/tilovell/archive/2010/02/26/misadventures-in-cachemetadata-wrapping-an-inner-activity-in-$c$c.aspx"相对=nofollow>这篇文章处理异常我得到。我不得不改变变量和孩子的活动是一个执行,所以这样的:

EDIT2: This article addressed the exception I was getting. I had to change both variable and child activity to be an "implementation", so this:

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

更改为此:

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

而引发的异常消失。不幸的是,它表明,以下行也绝对没有什么:

And caused all the exceptions to go away. Unfortunately, it revealed that the following line does absolutely nothing:

Predicate.ExpressionText = ExpressionText.Get(context);

更改运行过程中 VisualBasicValue 防爆pressionText 属性没有任何效果。我们可以用ILSpy揭示了为什么 - 除权pression文本仅被评估并将其转换为EX pression树时, CacheMetadata()被调用,在该点前pression还不知道,这就是为什么我使用的参数的构造函数初始化其结晶前pression到一个空操作。我甚至尝试保存 NativeActivityMetadata 对象我在我自己的CacheMetadata覆盖的方法得到,然后使用反射来迫使呼叫 VisualBasicValue CacheMetadata(),但只是最后抛出一个不同的神秘异常(暧昧的比赛中。型AmbiguousMatchException的)。

Changing the ExpressionText property of a VisualBasicValue during runtime has no effect. A quick check with ILSpy reveals why - the expression text is only evaluated and converted to an expression tree when CacheMetadata() is called, at which point the expression is not yet know, which is why I used the parameterless constructor which initialized and crystallized the expression to a no-op. I even tried saving the NativeActivityMetadata object I got in my own CacheMetadata overridden method and then use reflection to force a call to VisualBasicValue's CacheMetadata(), but that just ended up throwing a different cryptic exception ("Ambiguous match found." of type AmbiguousMatchException).

在这一点上似乎并没有能够充分整合动态EX pression成一个工作流程,公开所有调查范围内的变量给它。我想我会在我的评估 NativeEval 类中类中使用的方法。

At this point it doesn't seem possible to fully integrate a dynamic expression into a workflow, exposing all the in-scope variables to it. I guess I'll have the method used in my Eval class within the NativeEval class.

推荐答案

我最终使用的下列活动。它不能访问工作流的变量,而是接受一个参数值,可以使用相同的名称内的动态EX pression。除此之外,它的工作原理pretty的好。

I ended up using the following activity. It can't access the workflow's variables, instead it accepts a single argument 'Value' that can be used by the same name inside the dynamic expression. Other than that it works pretty well.

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);
        }
    }
}

修改:更新了类,包括完整的装配和命名空间的进口,免得你得到可怕的(和无益的)错误消息:

EDIT: Updated the class to include complete assembly and namespace imports, lest you get the dreaded (and unhelpful) error message:

值未声明。它可能无法访问由于其保护水平。

'Value' is not declared. It may be inaccessible due to its protection level.

此外,外移动防爆pressionEvaluator类,并使其公开,这样你就可以用它WF之外,像这样:

Also, moved the ExpressionEvaluator class outside and made it public, so you can used it outside of WF, like so:

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

这将返回:

6.28318530717959

6.28318530717959

这篇关于WF4:我如何评价在运行时才知道的当然pression?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆