Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Parsing and evaluating expressions both use IExpressionContext objects that indicate the environment available to the expression. An expression Salary + Bonus references two identifiers, which must be available in the context or expression-parsing in strict mode fails.

In most cases, the context will be an IMetaClass while parsing and an IDataObject during evaluation. The properties of the class or object are those against which the parser and evaluator will validate.

Other objects

A product will generally encounter contexts when working with Delegate Expression. There are two types of objects that the product can extract from the context:

  • Transient objects that are specific to the call.

  • Singleton services provided by Quino and the product.

Transient objects

The context contains zero or more transient objects, usually an IDataSession and IDataObject but also sometimes an ILanguage that indicates the preferred language, if different from the user's selected language (e.g. in some reporting functions).

Most products will use builder methods that pass type-safe business objects to product code as parameters. See Using generated code for more information.

However, a product can get the session with context.TryGetSession() and the underlying object with context.TryGetInstance<IDataObject>(). See Accessing the object in the context for more information.

Singleton Services

An expression function can always directly inject and use singleton services. This is the "proper" way to access services (see the chapter on the IOC for more information).

The following example shows how to inject a service into a metadata builder and then use it from an expression.

The overload of CreateDelegate used here doesn't even provide the context to the product. Instead, it extracts the Person from the context and calls the product code only if it's actually present.

Code Block
internal class GeneratedPersonBuilder : GeneratedProductMetadataBuilderBase
{
  public GeneratedPersonBuilder([NotNull] IProductService productService)
  {
    _productService = productService;
  }

  protected override void AddProperties()
  {
    Metadata.Person.FormattedAddress.SetValueExpression(
      f => f.CreateDelegate<Person>(
        p => _productService.GetFormattedAddress(p)
      )
    );
  }

  private readonly IProductService _productService;
}

The example above is clean and straightforward, but a bit more verbose, especially when scaled to multiple services and properties.

Another way of getting at the service is to extract it from the context instead of injecting it. As noted above, this may make it more difficult to determine dependencies, but it's up to the product to decide how to use its services.

Code Block
internal class GeneratedPersonBuilder : GeneratedProductMetadataBuilderBase
{
  protected override void AddProperties()
  {
    Metadata.Person.FormattedAddress.SetValueExpression(
      f => f.CreateDelegate<Person>(
        (c, p) => c.GetSingle<IProductService>().GetFormattedAddress(p)
      )
    );
  }
}

The example above calls GetSingle(), which throws an exception if the IOC is not available to the context.

During initialization, Quino tests some expressions (e.g. for default values) with an empty context in order to determine whether they are "static" (e.g. to determine whether they can be mapped to the database as part of schema-migration).

Therefore, products must be careful not to assume that the context will contain anything.

In this case, it's fine to call GetSingle() because the product can correctly assume that if the context contains a Person, then it also has access to the IOC.

However, if the expression were not dependent on anything else, or if the product needed to be more careful, then it can use TryGetSingle instead.

In either case, if there is an IOC in the context, but the requested object is not registered, then the call crashes, just as any other failed call to GetInstance() would crash. This is by design, as a product using an unregistered service is a program error, not a situation to be handled.

Because the following implementation depends on the Person as well, the product can rely on Quino to handle empty-context situations correctly.

Code Block
internal class GeneratedPersonBuilder : GeneratedProductMetadataBuilderBase
{
  protected override void AddProperties()
  {
    Metadata.Person.FormattedAddress.SetValueExpression(
      f => f.CreateDelegate<Person>(
        (c, p) => 
        {
          if (c.TryGetSingle<IProductService>(out var s))
          {
            return s.GetFormattedAddress(p);
          }

          return string.Empty;
        }
      )
    );
  }
}

In some cases, though, the product has to be more careful about the result. If it always returns a value, then Quino will assume that it always has the same value (i.e. is "static") and may make incorrect assumptions about the code.

In these cases, the product can return not just the result, but also a Boolean that indicates whether the value could be calculated.

Quino provides an overload that supports a tuple result, where the first element is the result and the second element is a Boolean value indicating whether the value could be calculated.

The following example shows how a product can use this overload to return a complete answer.

Code Block
internal class GeneratedPersonBuilder : GeneratedProductMetadataBuilderBase
{
  protected override void AddProperties()
  {
    Metadata.Person.FormattedAddress.SetValueExpression(
      f => f.CreateDelegate<Person>(
        (c, p) => {
          if (c.TryGetSingle<IProductService>(out var s))
          {
            return (s.GetFormattedAddress(p), true);
          }

          return (null, false);
        }
      )
    );
  }
}

...

Identifiers

Any text not contained in quotes and adhering to the following rules is an identifier that refers to data in the context in which the expression is evaluated.

An identifier:

  • Includes one or more letters (a-z or A-Z), digits (0-9) or underscores

  • Starts with at least one letter

For the programmers, the following snippets are from the official grammar:

Code Block
ID : LETTER (LETTER|DIGIT|'_')*;
fragment DIGIT : '0'..'9';
fragment LETTER : ('a'..'z'|'A'..'Z');

Chaining

Identifiers can be chained together using dots (".") to reference functions or identifiers in nested namespaces.

For example:

  • Trim(LastName)

  • Trim(Company.Address.City)

  • CreateGuid(Company.Address.City)

Technischer Hintergrund

Um zu verstehen welche Entitäten in der jeweiligen Expression angesprochen werden können, sehen Sie die Technischen Hintergründe in:

DevOps -> Quino 10 -> Developer Documentation -> Expressions (Nur für Entwickler zugänglich)