This change has been made in the course of larger refactorings.
If you use these properties to customize a default QueryParser (created via QueryParser.CreateDefault), you can cast the result of the NodeTypeProvider property to CompoundNodeTypeProvider and manipulate its InnerProviders property (adding new providers/registries or removing existing ones).
It is recommended, however, to create customized QueryParsers as follows:
private static IQueryParser CreateQueryParser () { var customNodeTypeRegistry = new MethodInfoBasedNodeTypeRegistry(); // Register custom node parsers here: // customNodeTypeRegistry.Register (MyExpressionNode.SupportedMethods, typeof (MyExpressionNode)); // Alternatively, use the CreateFromTypes factory method. // Use MethodNameBasedNodeTypeRegistry to register parsers by query operator name instead of MethodInfo. var nodeTypeProvider = ExpressionTreeParser.CreateDefaultNodeTypeProvider (); nodeTypeProvider.InnerProviders.Add (customNodeTypeRegistry); var transformerRegistry = ExpressionTransformerRegistry.CreateDefault (); // Register custom expression transformers here: // transformerRegistry.Register (new MyExpressionTransformer()); var processor = ExpressionTreeParser.CreateDefaultProcessor (transformerRegistry); // Add custom processors here: // processor.InnerProcessors.Add (new MyExpressionTreeProcessor()); var expressionTreeParser = new ExpressionTreeParser (nodeTypeProvider, processor); var queryParser = new QueryParser (expressionTreeParser); return queryParser; }
That way, the NodeTypeProvider property needn't be used for customization.
This change has been made in order to support Contains methods on custom collection types by default.
Previously, those methods throw a KeyNotFoundException.
This has been changed in order to provide for more efficient composability.
The MethodCallExpressionNodeTypeRegistry has been replaced by an interface with three implementations: INodeTypeProvider, MethodInfoBasedNodeTypeRegistry, MethodNameBasedNodeTypeRegistry, and CompoundNodeTypeProvider. The MethodCallExpressionNodeTypeRegistry.CreateDefault method has been replaced by ExpressionTreeParser.CreateDefaultNodeTypeProvider, which now creates a CompoundNodeTypeProvider holding default instances of MethodInfoBasedNodeTypeRegistry and MethodNameBasedNodeTypeRegistry. Change the defaults by adding, inserting, or removing instances to/from the default CompoundNodeTypeProvider (see below).
The IExpressionTreeProcessingStep interface is now called IExpressionTreeProcessor. Instead of an array of IExpressionTreeProcessors, ExpressionTreeParser now accepts a single IExpressionTreeProcessor (which can be a CompoundExpressionTreeProcessor if more than one processor is needed). The ExpressionTreeParser.CreateDefaultProcessor() method creates an instance of CompoundExpressionTreeProcessor with partial evaluation and transformation steps already included. Change the defaults by adding, inserting, or removing instances to/from the default CompoundExpressionTreeProcessor.
Here is the recommended new way of creating a customized query parser:
private static IQueryParser CreateQueryParser () { var customNodeTypeRegistry = new MethodInfoBasedNodeTypeRegistry(); // Register custom node parsers here: // customNodeTypeRegistry.Register (MyExpressionNode.SupportedMethods, typeof (MyExpressionNode)); // Alternatively, use the CreateFromTypes factory method. // Use MethodNameBasedNodeTypeRegistry to register parsers by query operator name instead of MethodInfo. var nodeTypeProvider = ExpressionTreeParser.CreateDefaultNodeTypeProvider (); nodeTypeProvider.InnerProviders.Add (customNodeTypeRegistry); var transformerRegistry = ExpressionTransformerRegistry.CreateDefault (); // Register custom expression transformers here: // transformerRegistry.Register (new MyExpressionTransformer()); var processor = ExpressionTreeParser.CreateDefaultProcessor (transformerRegistry); // Add custom processors here: // processor.InnerProcessors.Add (new MyExpressionTreeProcessor()); var expressionTreeParser = new ExpressionTreeParser (nodeTypeProvider, processor); var queryParser = new QueryParser (expressionTreeParser); return queryParser; }
These changes were made in order to provide a more consistent and extensible design.
The ContainsExpressionNode included in the default query parser (and in the default node type provider created by ExpressionTreeParser.CreateDefaultNodeTypeProvider) is now registered by name (and filter) by default.
The filter is defined so that every method called "Contains" on a type implementing IEnumerable (apart from System.String) that has the right number of arguments is now detected to be a query operator with the semantics of Queryable.Contains. In the respective QueryModels, these methods are represented as instances of ContainsResultOperator.
Usually, an expression tree visitor can replace nodes that have type T with nodes of a derived type T', because T' can be used wherever T can be used.
This is not true, however, when a NewExpression has members associated with its argument expressions. Because the member's return type must exactly match the type of the argument node, exceptions can occur when replacing an argument expression with an expression of a more specific type.
To remedy this, the ExpressionTreeVisitor class (and its subclasses) now automatically detects when such a situation arises, and inserts the necessary conversions.
For example, consider the following NewExpression:
var item = Expression.New ( typeof (KeyValuePair<int, MyType>).GetConstructor (...), new[] { Expression.Constant (0), Expression.Constant (new MyType()) }, new[] { typeof (KeyValuePair<int, MyType>).GetProperty ("Key"), KeyValuePair<int, MyType>).GetProperty ("Value") });
If, in this example, a visitor replaces the constant expression of type MyType with a constant expression of type MyDerivedType, an exception would be thrown in ExpressionTreeVisitor.VisitNewExpression in previous versions of re-linq ("Argument type 'MyDerivedType' does not match the corresponding member type 'MyType'."). This is now changed - the VisitNewExpression automatically inserts a conversion from MyDerivedType to MyType:
var item = Expression.New ( typeof (KeyValuePair<int, MyType>).GetConstructor (...), new[] { Expression.Constant (0), Expression.Convert (Expression.Constant (new MyDerivedType()), typeof (MyType)) }, new[] { typeof (KeyValuePair<int, MyType>).GetProperty ("Key"), KeyValuePair<int, MyType>).GetProperty ("Value") });
Previously, the re-motion build stripped out the "Remarks" sections from the Intellisense XML files created by the C# compiler. The reason for this was to improve the size of the build - the "Remarks" items weren't used by Visual Studio Intellisense anyway.
Nowadays, tools are able to show these sections, so we're adding them to the XML files again.
Consider the following example (from http://groups.google.com/group/nhusers/browse_thread/thread/7289619c4eceaf2b):
from u in db.Users
select new KeyValuePair<string, DateTime>(u.Name, u.RegisteredAt) into nameRegistered
order by nameRegistered.Value
The provider cannot know that u.Name goes into nameRegistered.Value. To allow for that, add a custom transformation that adds the metadata to the ctor call:
NewExpression ( ctorof (KeyValuePair<TK,TV>(TK,TV)), new[] { keyExpr, valueExpr }, new[] { memberof (KeyValuePair<TK,TV>.Key), memberof (KeyValuePair<TK,TV>.Value) })
Transformations such as the one described above are added a set of classes: KeyValuePairNewExpressionTransformer, and DictionaryEntryTransformer, and TupleNewExpressionTransformer, The transformations, which are enabled by default, add the MemberInfo metadata to NewExpressions for KeyValuePair<TKey, TValue>, DictionaryEntry, and the .NET Tuple types.
To implement such a transformation for custom types, derive from MemberAddingNewExpressionTransformerBase.
For LINQ providers customizing the expression tree preprocessing steps applied by the re-linq front-end, add instances of the transformer classes to the ExpressionTransformationStep.
When the evaluator detects a ConstantExpression with an IQueryable value, the ConstantExpression should be replaced by the IQueryable's expression.
Consider the following example:
var x = from p in Persons where p.Surname == "Freud" select p; var y = from p in Persons where x.Contains (p) select p;
With this new feature, the second query will appear to the LINQ provider as follows:
var y = from p in Persons where (from p in Persons where p.Surname == "Freud" select p).Contains (p) select p;
This has been integrated into the PartialEvaluatingExpressionTreeVisitor because the inlined expression (usually) also needs to be partially evaluated anyway.
The MethodCallExpressionNodeTypeRegistry has been replaced by an interface (INodeTypeProvider) with several implementations. One of them, the MethodNameBasedNodeTypeRegistry, provides support for registering expression node parsers by name (rather than MethodInfo). Since the method name is usually not enough to distinguish query operators, a registration also contains a filter predicate that determines whether a node parser is suitable for parsing a specific query operator method.
Consider the following example:
public class MyContainsExpressionNode : ResultOperatorExpressionNodeBase { public static readonly MethodInfo[] SupportedMethods = new[] { GetSupportedMethod (() => Queryable.Contains<object> (null, null)), GetSupportedMethod (() => Enumerable.Contains<object> (null, null)), }; public static readonly NameBasedRegistrationInfo[] SupportedMethodNames = new[] { new NameBasedRegistrationInfo ( "Contains", mi => mi.DeclaringType != typeof (string) && typeof (IEnumerable).IsAssignableFrom (mi.DeclaringType) && (mi.IsStatic && mi.GetParameters().Length == 2 || !mi.IsStatic && mi.GetParameters().Length == 1)) }; // ... }
This code defines that the given MyContainsExpressionNode is registered for the Contains query operators defined by the Queryable and Enumerable classes, and, in addition, for all methods called "Contains" (with the right argument count) declared by classes implementing IEnumerable. Only "string" is exempt, since string.Contains is usually not desired to be seen as a query operator.
(This code is how the ContainsExpressionNode class is currently defined in re-linq.)
To use such a custom expression node parser, create a MethodNameBasedNodeTypeRegistry when creating the QueryParser in your LINQ provider.
private static IQueryParser CreateQueryParser () { var customNodeTypeRegistry = new MethodInfoBasedNodeTypeRegistry(); // Register custom node parsers here: // customNodeTypeRegistry.Register (MyExpressionNode.SupportedMethods, typeof (MyExpressionNode)); // Alternatively, use the CreateFromTypes factory method. // Use MethodNameBasedNodeTypeRegistry to register parsers by query operator name instead of MethodInfo. var nodeTypeProvider = ExpressionTreeParser.CreateDefaultNodeTypeProvider (); nodeTypeProvider.InnerProviders.Add (customNodeTypeRegistry); var transformerRegistry = ExpressionTransformerRegistry.CreateDefault (); // Register custom expression transformers here: // transformerRegistry.Register (new MyExpressionTransformer()); var processor = ExpressionTreeParser.CreateDefaultProcessor (transformerRegistry); // Add custom processors here: // processor.InnerProcessors.Add (new MyExpressionTreeProcessor()); var expressionTreeParser = new ExpressionTreeParser (nodeTypeProvider, processor); var queryParser = new QueryParser (expressionTreeParser); return queryParser; }