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.
(Note that QueryParser and DefaultQueryProvider do not change state between different queries, so they can be stored and reused with "singleton" semantics.)