Provide a way to extend the customize the expression processing pipeline in the re-linq front-end

Description

A new mechanism has been added that allows re-linq-based providers to customize the expression processing pipeline in the re-linq front-end. This feature can be used to implement complex expression transformations (eg., expression visitor runs), to remove the partial evaluation feature included in the expression pipeline by default, or to add an additional partial evaluation step after the transformation step has run.

Note that for simple expression transformations, the predefined expression transformation support should be used as it is more efficient than an additional expression tree run.

The following classes and interfaces are involved:

  • Interface IExpressionTreeProcessingStep: Implement this interface to define a custom processing step. When creating a QueryParser, include this step in the pipeline. The Process method will then be called for the root node of the expression tree when a query is parsed.

  • Class PartialEvaluationStep: This is a predefined step that evaluates parts of an expression tree in-memory. An instance of this step is automatically included in the pipeline created by ExpressionTreeParser.CreateDefaultProcessingSteps.

  • Class ExpressionTransformationStep: This is a predefined step that executes a sequence of expression transformations when a query is parsed. An instance of this step is automatically included in the pipeline created by ExpressionTreeParser.CreateDefaultProcessingSteps.

To customize the processing steps executed by a LINQ provider, use a QueryParser that includes the steps to be processed. You can use the ExpressionTreeParser.CreateDefaultProcessingSteps method to get the steps included by default.

The following example creates a highly customizable instance of a class MyQueryable<T>, which has a custom processing step registered after all default steps have executed. 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 public static MyQueryable<T> CreateQuery<T>() { // include default nodes var nodeTypeRegistry = MethodCallExpressionNodeTypeRegistry.CreateDefault(); // include default transformations, and add new ones var transformerRegistry = ExpressionTransformerRegistry.CreateDefault(); // extend default pipeline with a custom step var defaultProcessingSteps = ExpressionTreeParser.CreateDefaultProcessingSteps (transformerRegistry); var processingSteps = new List<IExpressionTreeProcessingStep> (defaultProcessingSteps) { new CustomStep () }.ToArray(); // 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

None

Time tracking

0m

Components

Fix versions

Priority

Normal