re-linq now reserves ExpressionType values from 100000 to 150000 for its own expressions. When a custom LINQ provider derives its own Expression instances, it can use 150001 and above.
Subclasses of ExtensionExpression can decide whether to use the default value of 150000 or a custom one by choosing the appropriate constructor.
This change was needed in order to enable re-linq users to apply light-weight transformations using IExpressionTransformer<T> to re-linq-specific expressions.
This has been done for consistency with other re-linq parser classes. To create a default QueryParser, use QueryParser.CreateDefault().
QueryProviderBase, DefaultQueryProvider, and QueryableBase<T> now require an instance of IQueryParser to be passed into their constructors. This has been caused by refactorings made in the course of the addition of new features, and it is easily remedied.
To create a default query parser:
var queryParser = QueryParser.CreateDefault();
To create a customized query parser:
// include default nodes var nodeTypeRegistry = MethodCallExpressionNodeTypeRegistry.CreateDefault(); [customize node parsers here] // include default transformations var transformerRegistry = ExpressionTransformerRegistry.CreateDefault(); [customize transformers here] // use default pipeline var processingSteps = ExpressionTreeParser.CreateDefaultProcessingSteps (transformerRegistry); [customize pipeline steps here] // create an ordinary QueryParser that uses the objects defined so far var expressionTreeParser = new ExpressionTreeParser (nodeTypeRegistry, processingSteps); var queryParser = new QueryParser (expressionTreeParser);
(Note that QueryParser does not change state between different queries, so it can be stored and reused with "singleton" semantics. It is of course possible to use a dependency injection container rather than manually composing the parser.)
The reason for this change is that QueryProviderBase does not control what IQueryParser is used to parse the queries. Therefore, it has no access to the ExpressionTreeParser (which might event not be used at all).
To access the MethodCallExpressionNodeTypeRegistry previously available via the ExpressionTreeParser, you have the following options:
Previously, expressions such as SubQueryExpression, QuerySourceReferenceExpression, and ExtensionExpression all used non-unique default values for their NodeType property.
This has now changed. re-linq now reserves ExpressionType values from 100000 to 150000 for its own expressions. Custom LINQ providers can use 150001 and above.
Subclasses of ExtensionExpression can decide whether to use the default value of 150000 or a custom one by choosing the appropriate constructor.
This change was needed in order to enable re-linq users to apply light-weight transformations using IExpressionTransformer<T> to re-linq-specific expressions.
Due to several refactorings in the re-linq front-end, the QueryParser normally used by the query provider can now be replaced by a custom implementation of the IQueryParser interface. This enables full control over the parsing process.
Replace the query parser to implement post-processing of the QueryModel before it gets passed to the IQueryExecutor, to cache parsed queries, or in similar situations.
To preprocess the parsed expression tree, light-weight transformations based on IExpressionTransformer<T> and ExpressionTransformerRegistry and custom tree processing steps (IExpressionTreeProcessingStep) are usually better-suited as they allow more fine-grained customization.
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:
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.
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.)
Similar to the ReplacingExpressionTreeVisitor, the MultiReplacingExpressionTreeVisitor replaces expression nodes, but it adds the possibility of replacing more than one expression at once.
Expressions are also replaced within subqueries; the sub-QueryModel is changed by the replacement operations, it is not copied. The replacement node is not recursively searched for occurrences of Expression nodes to be replaced.
Support for query combinations involving the invocation of LambdaExpressions similar to the following:
Cooks.Where (c => ((Func<Cook, bool>) (c1 => c1.Name != null)) (c))
In this case, a LambdaExpression is constructed and immediately invoked in the Where condition. This can be detected and inlined in order to produce an expression equivalent to the Where condition in the following query:
Cooks.Where (c => c.Name != null)
(In this example, the C# compiler adds a trivial Convert expression that converts the constructed lambda to its own type. The transformation should be able to handle this.)
This is useful for specification libraries that need to combine predicates that are given in the form of LambdaExpressions, as in the following example:
Expression<Func<Order, bool>> predicate1 = o => o.OrderNumber > 100; Expression<Func<Order, bool>> predicate2 = o => o.OrderDate > new DateTime (2010, 01, 01); var combinedPredicate = Expression.Lambda<Func<Order, bool>> ( Expression.AndAlso ( predicate1.Body, Expression.Invoke (predicate2, predicate1.Parameters.Cast<Expression>()), ), predicate1.Parameters); var query = Orders.Where (combinedPredicate);
The simplification described above is added via a transformation implemented in the InvocationOfLambdaExpressionTransformer class. The transformation, which is enabled by default, replaces expressions invoking a LambdaExpression with a simplified, inlined version of the LambdaExpression's body. (It cannot inline invocations of delegate constants.)
For LINQ providers customizing the expression tree preprocessing steps applied by the re-linq front-end, add an instance of the InvocationOfLambdaExpressionTransformer class to the ExpressionTransformationStep.
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:
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.
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.)
Support for Visual Basic .NET queries containing expressions that use the IsNothing(...) method (rather than the Is Nothing operator) is added via a transformation implemented in the VBInformationIsNothingExpressionTransformer class. The transformation, which is enabled by default, replaces expressions calling the IsNothing method to BinaryExpressions comparing with a null constant.
That way, expressions similar to the following will now produce "normalized" expression trees:
Where IsNothing(emp.ReportsTo)
The expression tree created by the transformation is equivalent to that produced by:
Where emp.ReportsTo Is Nothing
For LINQ providers customizing the expression tree preprocessing steps applied by the re-linq front-end, add an instance of the VBInformationIsNothingExpressionTransformer class to the ExpressionTransformationStep.