Closed TehWardy closed 6 years ago
Hello @TehWardy ,
Here is a working example: https://dotnetfiddle.net/74j9j1
// @nuget: Z.Expressions.Eval
using System;
using System.Collections.Generic;
using System.Dynamic;
using Z.Expressions;
public class Program
{
public static void Main()
{
var activity = new Activity() { Foo = "A", Previous = new List<Activity>() { new Activity() { Foo = "Z" }}};
Console.WriteLine("Before: " + activity.Foo);
var function = Eval.Compile<Action<Activity>>("activity.Foo = activity.Previous[0].Foo", "activity");
function(activity);
Console.WriteLine("After: " + activity.Foo);
}
public class Activity
{
public string Foo { get; set; }
public List<Activity> Previous { get; set; }
}
}
Let me know if that helped you with your scenario.
Best Regards,
Jonathan
Hi Jonathon,
So lets assume that your code is correct (and it seems to look fine, and run ok). Oddly in a situation where activity was null I would expect the function call to fail but not the compile.
I seem to be getting null reference exception on the compile though with exactly the same params as you're passing in.
I have however managed to find a null reference in some of my code leading up to this call which having resolved that seemed to make the compile call issue go away. My question is really "how can this happen?" at this point.
I think i'm just going to have to battle on through with it and spend a lot of time stepping through with the debugger to figure out what's really the cause of the issue as i'm sure it can't be the ZZZ code.
The exceptions given are crazy cryptic though, is there anything that can be done to help with the clarity of them?
Many thanks,
Paul
On Mon, 29 Oct 2018 at 21:25, Jonathan Magnan notifications@github.com wrote:
Hello @TehWardy https://github.com/TehWardy ,
Here is a working example: https://dotnetfiddle.net/74j9j1
// @nuget: Z.Expressions.Eval using System;using System.Collections.Generic;using System.Dynamic;using Z.Expressions; public class Program { public static void Main() { var activity = new Activity() { Foo = "A", Previous = new List
() { new Activity() { Foo = "Z" }}}; Console.WriteLine("Before: " + activity.Foo); var function = Eval.Compile<Action<Activity>>("activity.Foo = activity.Previous[0].Foo", "activity"); function(activity); Console.WriteLine("After: " + activity.Foo);
}
public class Activity { public string Foo { get; set; }
public List<Activity> Previous { get; set; }
} }
Let me know if that helped you with your scenario.
Best Regards,
Jonathan
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/zzzprojects/Eval-Expression.NET/issues/45#issuecomment-434086080, or mute the thread https://github.com/notifications/unsubscribe-auth/AGkmFl91qXiGtJNqUm3xxyfAmqt8lVD1ks5up3I6gaJpZM4X_XUO .
Hello Paul,
We try every month to improve our compiler to provide better error messages. I will add this issue to the list my developer will look this month to improve.
There is a huge difference between both of our code
Your: Eval.Compile("activity.Foo = activity.Previous[0].Foo", "activity");
Mine: Eval.Compile<Action<Activity>>("activity.Foo = activity.Previous[0].Foo", "activity");
In my version, the compiler know that the first parameter named "activity" is of type Activity
. In your version, the compiler probably assume it's of type object which lead to a null reference (could be for another reason).
Let me know if that make sense.
How do i deal with polymorphic scenarios like this ....
class Activity
{
public Action<Activity> Assign { get; set; }
public Activity[] Previous { get; set; }
...
}
class FooActivity : Activity
{
public Foo Foo { get; set; }
}
// assuming I have done the work and I know the current activity instance is definitely a FooActivity and not just a regular Activity I have this need to dynamically build an Action
var method = Eval.Compile<Action<Activity>>("activity.Foo = activity.Previous[0].Foo", "activity");
As discussed previously (by email I believe), I'm using this to dynamically copy values computed in workflow activities from the previous to the current based on "Link Expression arrays".
in short I have something like this ...
var flow = {
Activities = new Activity[] { ... },
Links = new Link[] { ... }
}
Links have 3 properties "source", "destination", and "expression" and all this data is provided to the back end as a json blob which i then parse and process.
The expressions I receive are like this ...
{destination}.Foo = {source}.Foo
... which I translate to ...
activity.Foo = activity.Previous[0].Foo
... Neither of the objects needs to be specifically of type Activity but can both be of any type derived from Activity. This usually means that my expressions contain things that Activity doesn't and it causes me some problems.
so I have trying to dynamically invoke the compile method like this ...
// get the generic compile method with right signature
static MethodInfo compile = typeof(Eval).GetMethod("Compile", new Type[] { typeof(string), typeof(string[]) });
// make the generic version filling in the current activity type
var dynamicCompile = compile.MakeGenericMethod(typeof(Action<>).MakeGenericType(activity.GetType()));
// return a dynamic action cast to the generic base type for later execution
var dynamicMethod = (Action<Activity>)dynamicCompile.Invoke(null, new object[] { script, new[] { "activity" } });
... and that last line is where things fall over because activity (the base type) has an array "Previous" of type Activity which usually will contain things that derive from but are not actually Activity themselves.
I seem to be getting this null reference exception because of this issue around polymorphism in the data structure i'm using.
Hello @TehWardy ,
If I understand well, even the C# compiler will not like it.
A previous
item is of type Activity
and you try to use a property of type FooActivity
without casting it.
It's possible on your side to cast it? by example: https://dotnetfiddle.net/UwE89l
Otherwise, another way is maybe perhaps using ExpandoObject that could I believe support this kind of scenario without casting but then, your object will not be strongly typed.
Ah nice ... i wasn't aware i could do that ... This is a simple change ... bit of reflection involved in wrapping the {source} replacement i'm already doing but totally reasonable given the context in which i'm operating.
Am I correct in understanding then that Eval.Compile is essentially just calling the roslyn API under the bonnet? Also is there a way to ensure a level of system security by forcing only operations on the given parameters can be performed and not just any random expression like ...
File.OpenWrite("web.config")
can't seem to get this to work so I farmed this one liner out to it's own method ...
Action<T> Compile<T>(string script) where T : Activity
{
return Eval.Compile<Action<T>>(script, "activity");
}
if I put a breakpoint on that line and check typeof(T) T is in fact a type that inherits from Activity (hense how I got here in the first place. And script is (for example)
((FooActivity)activity).Object.Companies = ((BarActivity)activity.Previous[0]).Result;
So the call is like ...
Compile<FooActivity>(script);
Then it gives me this exception ...
Value cannot be null. Parameter name: type
at System.Linq.Expressions.Expression.Convert(Expression expression, Type type, MethodInfo method)
at .(ExpressionScope , SyntaxNode , Expression , Boolean )
at .(ExpressionScope , SyntaxNode , Expression , Boolean )
at .(ExpressionScope , SyntaxNode , Expression , Boolean )
at .(ExpressionScope , SyntaxNode , Expression , Boolean )
at .(ExpressionScope , SyntaxNode )
at .(ExpressionScope , SyntaxNode , Expression , Boolean )
at .(ExpressionScope , SyntaxNode , Expression , Boolean )
at .(ExpressionScope , SyntaxNode , Expression , Boolean )
at Z.Expressions.CodeCompiler.CSharp.ExpressionParser.ParseSyntax(ExpressionScope scope, SyntaxNode node, Type resultType)
at .[](EvalContext , String , IDictionary`2 , Type , EvalCompilerParameterKind , Boolean , Boolean )
at Z.Expressions.EvalContext.Compile[TDelegate](String code, String[] parameterNames)
at Z.Expressions.Eval.Compile[TDelegate](String code, String[] parameterNames)
....
Having dug in to this I think the problem is in the dependency graph. I've added a bunch of assemblies as references to my project that are likely (from the point of this projects point of view anyway) ... only used in this context.
I think the issue is that the types i'm casting to may or may not exist in the app domain. Does Eval have a means to provide a dependency resolver or something for scripts so it can scrape for a know type def?
Am I correct in understanding then that Eval.Compile is essentially just calling the roslyn API under the bonnet?
You are 100% wrong. We have written our own CodeAnalisys
and CodeCompiler
that mostly expression tree: https://expressiontree-tutorial.net/
We might release another product in the future that use Roslyn
but this library will never use it.
Also is there a way to ensure a level of system security by forcing only operations on the given parameters can be performed and not just any random expression like ...
It's possible to register only some type by default using the following code:
var evalContext = new EvalContext();
evalContext.UnregisterAll();
evalContext.RegisterDefaultAliasSafe();
The RegisterDefaultAliasSafe
should only register type that should not have any impact such as string, math, and regex function (there is a lot of more!). You can also choose to register what you want your user get access.
So if the type doesn't exists, the user should not be in able to access it.
For your second post,
You can register type or assembly using Register
methods: https://eval-expression.net/register-unregister
If it still doesn't work, do you think you will be able to provide a project sample with the issue? It will be easier for us to look at it: info@zzzprojects.com
Oh that's interesting @JonathanMagnan ... Dam you guys went all out huh!!
Does contructing and operating on an EvalContext do something with the current appdomain under the bonnet.
So if I register a ton of assemblies in it in say static void main of a console app will all calls to Eval.Compile then use what I setup in that EvalContext as shown here ...
https://eval-expression.net/options
// using Z.Expressions; // Don't forget to include this.
var context = new EvalContext();
context.IncludeMemberFromAllParameters = true;
Eval.Execute<bool>("catPropertyName == dogPropertyName", cat, dog);
... this implies that Eval.Execute
This also implies that doing this on multiple threads might prove problematic?
Hello @TehWardy ,
I'm sorry, I'm not sure to understand the question.
In multi-threading scenario, you should only configure the default EvalContext (the one used for Eval.Execute
) once. Usually on application Startup.
EvalManager.DefaultContext.IncludeMemberFromAllParameters = true;
Eval.Execute<bool>("catPropertyName == dogPropertyName", cat, dog);
If every thread have some special configuration, you need to create a new EvalContext inside the thread and use this instance instead
var context = new EvalContext();
context.IncludeMemberFromAllParameters = true;
context.Execute<bool>("catPropertyName == dogPropertyName", cat, dog);
I hope that somewhat answer your question.
Ah perfect :)
That works for me!
So I hit this issue. I made the following call ...
var function = Eval.Compile("activity.Foo = activity.Previous[0].Foo", "activity");
... and got this back ...
The next line in the reported exception was the line in my code that made this call. I'm not sure how I can get a null reference exception when all i did was give it a simple string pair.
Any ideas?