Release notes for re-motion version 1.13.93

List of Issues

Breaking Changes

New Features

Details

[RM-3723] The NodeTypeRegistry properties on QueryParser and ExpressionTreeParser have been replaced by NodeTypeProvider properties

Component/s: Data.Linq
Issue Type: Breaking Change
Resolution: Fixed
Status: Closed


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.

[RM-3722] All "Contains" methods on types implementing IEnumerable (apart from String) are now represented by the ContainsResultOperator

Component/s: Data.Linq
Issue Type: Breaking Change
Resolution: Fixed
Status: Closed


This change has been made in order to support Contains methods on custom collection types by default.

[RM-3721] INodeTypeProvider implementations (MethodInfoBasedNodeTypeRegistry, MethodNameBasedNodeTypeRegistry, etc.) now return null from their GetNodeType methods when no node type was found

Component/s: Data.Linq
Issue Type: Breaking Change
Resolution: Fixed
Status: Closed


Previously, those methods throw a KeyNotFoundException.
This has been changed in order to provide for more efficient composability.

[RM-3720] The code required to create a customized query parser has changed (MethodCallExpressionNodeTypeRegistry, IExpressionTreeProcessingStep, ExpressionTreeParser)

Component/s: Data.Linq
Issue Type: Breaking Change
Resolution: Fixed
Status: Closed


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.

[RM-3719] re-linq now detects "Contains" methods on all types implementing IEnumerable as query operators

Component/s: Data.Linq
Issue Type: New Feature
Resolution: Fixed
Status: Closed


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.

[RM-3711] ExpressionTreeVisitor now automatically converts the arguments of NewExpressions if needed by the associated members

Component/s: Data.Linq
Issue Type: New Feature
Resolution: Fixed
Status: Closed


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") });

[RM-3707] Keep full API docs in Intellisense files

Component/s: Build & Tooling Core Data Data.Linq Data.Linq for re-store Development DMS Mixins ObjectBinding ObjectBinding.Web Scripting Security SecurityManager Support Web Web.ExecutionEngine
Issue Type: New Feature
Resolution: Fixed
Status: Closed


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.

[RM-3630] Provide transformations that add member meta-info to common tuple-like constructor calls

Component/s: Data.Linq
Issue Type: New Feature
Resolution: Fixed
Status: Closed


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.

[RM-3472] Update partial evaluator to inline IQueryable ConstantExpressions in re-linq front end

Component/s: Data.Linq
Issue Type: New Feature
Resolution: Fixed
Status: Closed


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.

[RM-3341] Add a filtering mechanism to expression node types registered by names

Component/s: Data.Linq
Issue Type: New Feature
Resolution: Fixed
Status: Closed


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;
}