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().

Back to top Generated by DocFX