microsoft / Power-Fx

Power Fx low-code programming language
MIT License
3.21k stars 327 forks source link

Usage Question: Combining record(values) from several sources in c# #1895

Open kklocker opened 1 year ago

kklocker commented 1 year ago

Hi! Im working on a small c# project of mine in which I want to make it possible for the user to provide custom Json input to a formula, but I also want the engine evaluation to take as input some data from internal sources (dotnet classes).

I would gladly also take some feedback as to the general usage.. Consider this small example (from a test):


file class MyPoco
{
    public List<MyPocoLine> Values { get; set; } = new List<MyPocoLine>();
    public decimal MyFactor { get; set; }
}

file class MyPocoLine
{
    public MyPocoLine(int value) => Value = value;
    public int Value { get; }
}

var pocoInstance = new MyPoco() { Values = { new(1), new(2), new(3), new(4) }, MyFactor = 2 };

string expression = $"Sum(Values;Value) * MyFactor";

var engine = new RecalcEngine(new PowerFxConfig(Features.PowerFxV1));

var marshallerProvider = new ObjectMarshallerProvider();
marshallerProvider.TryGetMarshaller(typeof(MyPoco), new(), out var marshaller);

var formulaValue = (ObjectRecordValue)marshaller.Marshal(pocoInstance);
var result = engine.Eval(expression, formulaValue); // How could I additionally provide Json here? 
result.TryGetPrimitiveValue(out var primitiveResult); // primitiveResult is 20
var expected = pocoInstance.Values.Sum(f => f.Value) * pocoInstance.MyFactor; // expected is 20

I have gotten both dto / json context to work separately, but I don't quite understand how to combine this.

Any help is apreciated, thanks!

jas-valgotar commented 1 year ago

Hi Karl.

Are you looking for something that converts the JSON into formula values? then, FormulaValueJSON.FromJson() is something that may be helpful. https://github.com/microsoft/Power-Fx/blob/6d8a651a837cdf7f21713f4bf1810e160da7d19b/src/libraries/Microsoft.PowerFx.Json/FormulaValueJSON.cs#L20

The tests here demo it can be leveraged: https://github.com/microsoft/Power-Fx/blob/main/src/tests/Microsoft.PowerFx.Json.Tests/ParseJSONTests.cs

kklocker commented 1 year ago

@jas-valgotar Yes, I have had success with that :)

The problem is that I want to have available records/values ( forgive me, I'm a bit confused of what is what here) coming from both json and a dotnet-glass at the same time, i.e. I want to evaluate a formula in which the variables should be taken from either or the other

anderson-joyle commented 1 year ago

@kklocker Are you trying to combine all these records from dotnet-glass and json into a single bigger record or having multiple variables, each being a different record?

If it is the second case, maybe

engine.UpdateVariable("RecordN", formulaValue); // add a similar line to each of your other records
kklocker commented 1 year ago

@anderson-joyle I suspect more the latter, the point being that i want my app to not being dependent a priori of knowing what the json will contain.

Thank you, I will try that when i get time 😄

This might not be the intended use case, but I wanted to try :)

(Also: is there any nice intuitive way of understanding what a record vs tavle vs value is?)

jas-valgotar commented 1 year ago

Our Microsoft Learn page is in the making which will have a lot of information available soon so stay tuned :)

Meanwhile, To explain different values intuitively, There are broadly 3 types:

  1. Primitive Values: Basically, all the "Simple" values you can use inside the language as a building block, which are Number, Boolean, String, Date[Time], GUID, and Color.

  2. Record Values: (represented via {field: Value}) Think of this like something that represents a single row in a table (though it can exist independently also), and each column of the table is basically a "field" of the record. The field of the record can point to any "Values" (e.g. Primitive, Record, Or Table)

  3. Table Values: (represented via [] or Table(), so [{field: Value}, {field: Value}] or Table({field: Value}, {field: Value}])) This is a collection of records described above.

kklocker commented 1 year ago

@jas-valgotar Thank you! 😊 What about "FormulaValue"? I am trying to create some helper method to parse multiple different objects and pass that as context, using an object marshaller. But I am now confused, since it seems like I want a RecordValue, or perhaps even ObjectRecordValue, but engine.UpdateVariable(..) accepts FormulaValue. Soo I can definitely see there is some hierarchy here, but I am a bit confused as to what things are supposed to represent :)

image