We're updating the issue view to help you get more done. 

Provide a way to register custom expression transformers in the re-linq frontend

Description

A new mechanism has been added that allows re-linq-based providers to add custom, light-weight expression transformers to the front-end's query parsing pipeline. This feature can be used to implement simple expression transformers without having to bear the cost of an additional expression visitor run.

The following classes and interfaces are involved:

  • Interface IExpressionTransformationProvider: Low-level interface for classes providing transformations to be applied by the pipeline. Usually, the ExpressionTransformerRegistry class (see below) is used rather than custom implementations.

  • Class ExpressionTransformerRegistry: A default implementation of IExpressionTransformationProvider which keeps track of transformers implementing the IExpressionTransformer<T> interface. Use the CreateDefault factory method to create an instance of this class that already holds the default transformations defined by re-linq.

  • Interface IExpressionTransformer<T>: Implement this interface to define a custom transformation. The SupportedExpressionTypes property should return the expression node types handled by this transformer, or null if the transformer can handle all node types. T is the static (base) type of the expressions handled by the transformer. The Transform method implements the actual transformation.

  • Class ExpressionTransformationStep: Applies the transformations given by an implementation of IExpressionTransformationProvider to an expression tree. The transformations occur in post-order (transforming child expressions before parent expressions). When a transformation changes an expression, its child expressions and itself will have the matching transformations applied again (and may be transformed again). An instance of this step is included in the default pipeline created by ExpressionTreeParser.CreateDefaultProcessingSteps. The transformations by default run after partial evaluation of the expression tree.

To include a custom transformation in a LINQ provider, use a QueryParser that includes the transformation.

The following example creates a highly customizable instance of a class MyQueryable<T>, which has two custom transformers registered. It is of course possible to use a dependency injection container rather than manually composing the queryProvider.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public static MyQueryable<T> CreateQuery<T>() { // include default nodes var nodeTypeRegistry = MethodCallExpressionNodeTypeRegistry.CreateDefault(); // include default transformations, and add new ones var transformerRegistry = ExpressionTransformerRegistry.CreateDefault(); transformerRegistry.Register (new CustomTransformer1()); transformerRegistry.Register (new CustomTransformer2()); // use default pipeline var processingSteps = ExpressionTreeParser.CreateDefaultProcessingSteps (transformerRegistry); // create an ordinary QueryParser that uses the objects defined so far var expressionTreeParser = new ExpressionTreeParser (nodeTypeRegistry, processingSteps); var queryParser = new QueryParser (expressionTreeParser); // the query executor represents the LINQ provider's back-end var queryExecutor = new MyQueryExecutor(); // the query provider plumbs queryParser and queryExecutor together var queryProvider = new DefaultQueryProvider (queryParser, queryExecutor); // the queryable is the entry point in the query - and its execution return new MyQueryable<T> (queryProvider); }

(Note that QueryParser and DefaultQueryProvider do not change state between different queries, so they can be stored and reused with "singleton" semantics.)

Status

Assignee

Fabian Schmied

Reporter

Fabian Schmied

Labels

Time tracking

0m

Components

Fix versions

Priority

Normal