A product can integrate expressions that call C# code using IExpressionFactory.CreateDelegate()
(or one of its several extension methods).
Advantages and Drawbacks
Pure expressions
Standard expressions and functions have the following advantages:
...
The logic is limited to a single-statement (with some conditional logic, like that provided by the
If
function)Evaluation is not easily debugged (there's no source-level debugger for Quino expressions)
Delegate expressions
Delegate expressions (and Expression Eigene Funktionen) have the following advantages:
...
With those pros and cons in mind, the following document explains how to add C# delegates to expressions.
Context
The context passed to a delegate expression when evaluated contains the following objects by default:
...
A product can either use the session to get other dependencies or it can retrieve them from the IExpressionContext
directly with GetInstance()
. A product can also register its own IExpressionContextFactory
to control which objects are added.
Examples
Simple expression
Let's start with a calculated property that doesn't use a delegate. Instead, it just returns the value of another property:
Code Block |
---|
personBuilder .Add .CalculatedProperty("Seniority", MetaType.Integer) .Value("YearsActive"); |
Accessing the object in the context
The next example shows how to get this property with a delegate, but without any dependency on Modell Generieren.
...
It accesses the property by name rather than statically
It requires that the context contains an object of type
IDataObject
Indicating computability
In many cases the product will want to indicate that the expression cannot be evaluated with some contexts.
...
Code Block |
---|
person .Add .CalculatedProperty("Bonus", MetaType.Integer) .ValueExpression(TryGetBonus); // ... private bool TryGetBonus(IExpressionContext context, out object value) { if (context.TryGetInstance<Person>(out var person)) { if (person.Company != null) { if (context.TryGetSingle(out ISystemClock clock)) { if (person.HireDate < clock.UtcNow) { value = person.Company.GetBonus(person.HireDate.Year); return true; } } } } value = null; return return false; } |
Comparing parsed and delegate expressions
At the same time, you can see how powerful and useful the Expression Text Formatierung is: the GetSalutation()
method has a lot of hand-coding just to get the same behavior as the much shorter text-formatting expression.
...
Code Block |
---|
const string conditionText = "Salutation.IsFormal"; const string familiarText = "'<{Salutation.Text} {FirstName}>'"; const string formalText = "'<<{Salutation.Text} {Title}> {LastName}>'"; var condition = _expressionParser.CreateExpression( conditionText, true, metaClass ); var familiar = _expressionParser.CreateExpression( familiarText, true, metaClass ); var formal = _expressionParser.CreateExpression( formatText, true, metaClass ); // Use a pure, parsed expression text metaClass .Add .CalculatedProperty("SalutationText", MetaType.Text) .ValueExpression( $"If({conditionText}, {formatText}, {familiarText})" ); // Use a delegate with a strongly typed parameter metaClass .Add .CalculatedProperty("SalutationTextPureDelegate", MetaType.Text) .ValueExpression<Address>( GetSalutation ); private static object GetSalutation(Address address) { var salutation = address.Salutation; if (salutation != null) { var result = salutation.Text; if (salutation.IsFormal) { if (!string.IsNullOrEmpty(address.Title)) { if (string.IsNullOrEmpty(result)) { result = address.Title; } else { result += $" {address.Title}"; } } if (string.IsNullOrEmpty(address.Name)) { return result; } return result + $" {address.Name}"; } if (!string.IsNullOrEmpty(address.FirstName)) { if (string.IsNullOrEmpty(result)) { return address.FirstName; } result += $" {address.FirstName}"; } return result; } return string.Empty; } |
Mapping to SQL
Until the issue QNO-6478 provides more well-integrated support for mapping DelegateExpressions
to SQL, a product can build custom mapping with the following pattern.
...