I'm developing an application that works like an excel sheet: the user types a few values, and a lot of consequent calculation is done, in Blazor.
Right now I'm following this sequence (simplified code can be found at the end of question):
- Retrieve all expressions from DB into a List.
The expressions can be written by the user, in another section. For example, the user can insert that BMI formula is"= @Weight / @Height ^2"
- Each property call "RetrieveValue(propertyName)"
- Substitute refferences in expression for it's value
That means, if the expression is"= @Weight / @Height ^ 2"
, I'll replace '@Weight' and '@Height' for either user-typed values, or for values from other expressions - Evaluate using FLEE (fast lightweight expression evalutor)
I'd then send the expression "100 / 170 ^2" to be compiled and return it's value to the property
But it's taking too much time (around 300 evaluations taking 40 seconds), so I'm not sure this is the best method. I'm already storing an expression final form and it's result, so I don't have to evaluate again if it has already been evaluated.
What changes can I do to improve performance?
-
Expressions = expressionsLibrary.ToList();
-
public string? BMI { get { return RetrieveValue("BMI"); } set { return; } }
-
public string RetrieveValue(string propertyName) { // Retrieve property expression from list var result = Expressions.FirstOrDefault(e => d.Name == propertyName) ?? null; object value = result.Value; // Check for null if (result is null) { return null; } else if (!(result.Value is null) && result.ValueTrim().StartsWith('=')) { // Replace refferences for numeric valuess var replaced = ReplaceRefferences(result.Value)GetAwaiter().GetResult(); // If I didn't evaluate this expression yet, or it has changed if (string.IsNullOrEmpty(result.ReplacedValue) || resultado.ReplacedValue != replaced) { var index = Expressions.IndexOf(result); // Updates expression in list Expressions[index].ReplacedValue = replaced; // Evaluate value = Eval(replaced); // Updates value in list Expressions[index].EvaluatedValue = value; } else { value = result.EvaluatedValue; } } return Convert.ToString(value); }
-
private string Eval(String expression) { cExpressionContext context = new ExpressionContext(); context.ParserOptions.RecreateParser(); // Allow the expression to use all static public methods of System.Math context.Imports.AddType(typeof(Math)); Flee.PublicTypes.IDynamicExpression eDynamic = context.CompileDynamic(expression); return Convert.ToString(eDynamic.Evaluate()); }
I'm already storing an expression final form and it's result, so I don't have to evaluate again if it has already been evaluated. It works well with a few expressions per call (10 or so)