Delegate Expressions
Note: This documentation is a DRAFT.
An application can integrate expressions that call lambdas using IExpressionFactory.CreateDelegate()
(or one of its several extension methods).
Standard expressions and functions have the following advantages:
- They can be mapped to the database, improving ORM speed.
- They can be serialized in remoting drivers
- They can be analyzed and used by Quino to optimize other operations
Delegate expressions have the following advantages:
- They are executed locally in the ORM, against local data
- They can contain more complex logic
- They can be debugged
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:
- The
IDataSession
- The
IDataObject
to which the property belongs - The
ILanguage
that is currently selected in the UI
An application can either use the session to get other dependencies or it can retrieve them from the IExpressionContext
directly with GetInstance()
. An application can also register its own IExpressionContextFactory
to control which objects are added.
Examples
The following example shows a direct usage of the context:
// Get the IDataObject as a Person
person.AddCalculatedProperty("Seniority", MetaType.Integer, c => c.GetInstance<Person>().YearsActive);
This lambda will throw an exception if an object of type Person
does not exist in the context. Since this is such a common case, there's an overload that only calls the lambda if there is an object of the expected type.
// Get the IDataObject as a Person
person.AddCalculatedProperty<Person>("Seniority", MetaType.Integer, p => p.YearsActive);
An application can, of course, use a full-blown method instead of just a one-line lambda.
Since the application can get the underlying data object as a typed object, it then, of course has access to the rest of the object graph related to that object. For example,
person.AddCalculatedProperty<Person>("Bonus", MetaType.Integer, p => p.Company.GetBonus(person.HireDate.Year));
There's no magic to this: an application can manage all of this on its own as well. If, for example, the data model isn't so predictable (i.e. objects can be null or you're re-using logic for several types), then an application could protect itself with the following formulation.
person.AddCalculatedProperty<Person>("Bonus", MetaType.Integer, GetBonus);
// ...
private object GetBonus(IExpressionContext context)
{
var person = context.GetInstanceOrDefault<Person>();
if (person != null)
{
if (person.Company != null)
{
if (person.HireDate < context.GetInstance<IClock>().Now)
{
return person.Company.GetBonus(person.HireDate.Year);
}
}
}
return 0;
}
An application can put as much logic/work as it needs into a calculated property. In this case, we use the ValueGenerationFrequency
to cache the value.
person
.AddCalculatedProperty<Person>("TimeStuff", MetaType.Integer, GetTimeStuff)
.SetValueGenerationFrequency(ValueGenerationFrequency.Cached);
//...
private int GetTimeStuff(Person person)
{
foreach (var t in person.TimeEntries)
{
var project = t.Project;
var lead = project.Lead;
var address = lead.Address;
if (lead.Active)
{
return address.ZipCode;
}
// Otherwise, do other stuff...
}
};
To be clear: these are just the overloads provided by Quino, by default. An application is free to define extension methods that support mapping other lambda signatures to IExpressionFactory.CreateDelegate()
.