|
//---------------------------------------------------------------------
// <copyright file="ResourceBinder.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// <summary>
// Expression visitor that binds expression tree to resources
// </summary>
//
// @owner Microsoft
//---------------------------------------------------------------------
namespace System.Data.Services.Client
{
#region Namespaces.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
#endregion Namespaces.
/// <summary>
/// This class provides a Bind method that analyzes an input <see cref="Expression"/> and returns a bound tree.
/// </summary>
internal class ResourceBinder : DataServiceALinqExpressionVisitor
{
/// <summary>Analyzes and binds the specified expression.</summary>
/// <param name="e">Expression to bind.</param>
/// <returns>
/// The expression with bound nodes (annotated expressions used by
/// the Expression-to-URI translator).
/// </returns>
internal static Expression Bind(Expression e)
{
Debug.Assert(e != null, "e != null");
ResourceBinder rb = new ResourceBinder();
Expression boundExpression = rb.Visit(e);
VerifyKeyPredicates(boundExpression);
VerifyNotSelectManyProjection(boundExpression);
return boundExpression;
}
/// <summary>
/// Checks whether the specified <paramref name="expression"/> is
/// missing necessary key predicates.
/// </summary>
/// <param name="expression">Expression to check.</param>
/// <returns>
/// true if the expression is a navigation expression and doesn't
/// have the necessary key predicates on the associated resource
/// expression; false otherwise.
/// </returns>
internal static bool IsMissingKeyPredicates(Expression expression)
{
ResourceExpression re = expression as ResourceExpression;
if (re != null)
{
if (IsMissingKeyPredicates(re.Source))
{
return true;
}
if (re.Source != null)
{
ResourceSetExpression rse = re.Source as ResourceSetExpression;
if ((rse != null) && !rse.HasKeyPredicate)
{
return true;
}
}
}
return false;
}
/// <summary>
/// Verifies that all key predicates are assigned to the specified expression.
/// </summary>
/// <param name="e">Expression to verify.</param>
internal static void VerifyKeyPredicates(Expression e)
{
if (IsMissingKeyPredicates(e))
{
throw new NotSupportedException(Strings.ALinq_CantNavigateWithoutKeyPredicate);
}
}
/// <summary>Verifies that the specified <paramref name="expression"/> is not a projection based on SelectMany.</summary>
/// <param name="expression">Expression to check.</param>
internal static void VerifyNotSelectManyProjection(Expression expression)
{
Debug.Assert(expression != null, "expression != null");
// Check that there isn't a SelectMany projection (or if there is one,
// that there isn't an associated transparent scope for the resource
// set reference).
ResourceSetExpression resourceSet = expression as ResourceSetExpression;
if (resourceSet != null)
{
ProjectionQueryOptionExpression projection = resourceSet.Projection;
if (projection != null)
{
Debug.Assert(projection.Selector != null, "projection.Selector != null -- otherwise incorrectly constructed");
MethodCallExpression call = StripTo<MethodCallExpression>(projection.Selector.Body);
if (call != null && call.Method.Name == "SelectMany")
{
throw new NotSupportedException(Strings.ALinq_UnsupportedExpression(call));
}
}
else if (resourceSet.HasTransparentScope)
{
throw new NotSupportedException(Strings.ALinq_UnsupportedExpression(resourceSet));
}
}
}
/// <summary>Analyzes a predicate (Where clause).</summary>
/// <param name="mce"><see cref="MethodCallExpression"/> for a Where call.</param>
/// <returns>
/// An equivalent expression to <paramref name="mce"/>, possibly a different one with additional annotations.
/// </returns>
private static Expression AnalyzePredicate(MethodCallExpression mce)
{
Debug.Assert(mce != null, "mce != null -- caller couldn't have know the expression kind otherwise");
Debug.Assert(mce.Method.Name == "Where", "mce.Method.Name == 'Where' -- otherwise this isn't a predicate");
// Validate that the input is a resource set and retrieve the Lambda that defines the predicate
ResourceSetExpression input;
LambdaExpression le;
if (!TryGetResourceSetMethodArguments(mce, out input, out le))
{
// might have failed because of singleton, so throw better error if so.
ValidationRules.RequireNonSingleton(mce.Arguments[0]);
return mce;
}
//
// Valid predicate patterns are as follows:
// 1. A URI-compatible filter applied to the input resource set
// 2. A key-predicate filter applied to the input resource set
// - Additionally, key-predicate filters may be applied to any resource path component
// that does not already have a key-predicate filter, regardless of any filter applied
// to the current input resource set, if transparent scopes are present.
// - It is not valid to apply a key-predicate or a filter to a resource set
// for which key-predicate is already present.
// - It is valid to apply a filter to a resource set for which a filter already exists;
// such filters are AND'd together.
//
// [Key-predicate that targets a path component AND]* [Key-predicate over input | URI-compatible filter over input]+
List<Expression> conjuncts = new List<Expression>();
AddConjuncts(le.Body, conjuncts);
Dictionary<ResourceSetExpression, List<Expression>> predicatesByTarget = new Dictionary<ResourceSetExpression, List<Expression>>(ReferenceEqualityComparer<ResourceSetExpression>.Instance);
List<ResourceExpression> referencedInputs = new List<ResourceExpression>();
foreach (Expression e in conjuncts)
{
Expression reboundPredicate = InputBinder.Bind(e, input, le.Parameters[0], referencedInputs);
if (referencedInputs.Count > 1)
{
// UNSUPPORTED: A single clause cannot refer to more than one resource set
return mce;
}
ResourceSetExpression boundTarget = (referencedInputs.Count == 0 ? input : referencedInputs[0] as ResourceSetExpression);
if (boundTarget == null)
{
// UNSUPPORTED: Each clause must refer to a path component that is a resource set, not a singleton navigation property
return mce;
}
List<Expression> targetPredicates = null;
if (!predicatesByTarget.TryGetValue(boundTarget, out targetPredicates))
{
targetPredicates = new List<Expression>();
predicatesByTarget[boundTarget] = targetPredicates;
}
targetPredicates.Add(reboundPredicate);
referencedInputs.Clear();
}
conjuncts = null;
List<Expression> inputPredicates;
if (predicatesByTarget.TryGetValue(input, out inputPredicates))
{
predicatesByTarget.Remove(input);
}
else
{
inputPredicates = null;
}
foreach (KeyValuePair<ResourceSetExpression, List<Expression>> predicates in predicatesByTarget)
{
ResourceSetExpression target = predicates.Key;
List<Expression> clauses = predicates.Value;
Dictionary<PropertyInfo, ConstantExpression> keyValues;
if (!ExtractKeyPredicate(target, clauses, out keyValues) ||
clauses.Count > 0)
{
// UNSUPPORTED: Only key predicates may be applied to earlier path components
return mce;
}
// Earlier path components must be navigation sources, and navigation sources cannot have query options.
Debug.Assert(!target.HasQueryOptions, "Navigation source had query options?");
SetKeyPredicate(target, keyValues);
}
if (inputPredicates != null)
{
Dictionary<PropertyInfo, ConstantExpression> inputKeyValues;
if (ExtractKeyPredicate(input, inputPredicates, out inputKeyValues))
{
if (input.HasSequenceQueryOptions)
{
// SQLBUDT 616297: A key predicate cannot be applied if query options other than 'Expand' are present,
// so merge the key predicate into the filter query option instead.
Expression predicateFilter = BuildKeyPredicateFilter(input.CreateReference(), inputKeyValues);
inputPredicates.Add(predicateFilter);
}
else
{
SetKeyPredicate(input, inputKeyValues);
}
}
if (inputPredicates.Count > 0)
{
if (input.KeyPredicate != null)
{
// SQLBUDT 616297: a filter query option cannot be specified if a key predicate is present,
// so remove the existing key predicate and add it to inputPredicates as a filter. Note that
// this can only be done on the current resource set, where the filter query option is being
// applied, since earlier sets in the resource path are navigations that require a singleton,
// enforced by the presence of a key predicate.
Expression predicateFilter = BuildKeyPredicateFilter(input.CreateReference(), input.KeyPredicate);
inputPredicates.Add(predicateFilter);
input.KeyPredicate = null;
}
int start;
Expression newFilter;
if (input.Filter != null)
{
start = 0;
newFilter = input.Filter.Predicate;
}
else
{
start = 1;
newFilter = inputPredicates[0];
}
for (int idx = start; idx < inputPredicates.Count; idx++)
{
newFilter = Expression.And(newFilter, inputPredicates[idx]);
}
AddSequenceQueryOption(input, new FilterQueryOptionExpression(mce.Method.ReturnType, newFilter));
}
}
return input; // No need to adjust this.currentResource - filters are merged in all cases
}
private static void SetKeyPredicate(ResourceSetExpression rse, Dictionary<PropertyInfo, ConstantExpression> keyValues)
{
Debug.Assert(rse != null, "rse != null");
Debug.Assert(keyValues != null, "keyValues != null");
if (rse.KeyPredicate == null)
{
rse.KeyPredicate = new Dictionary<PropertyInfo, ConstantExpression>(EqualityComparer<PropertyInfo>.Default);
}
foreach(var g in keyValues)
{
if (rse.KeyPredicate.Keys.Contains(g.Key))
{
//UNSUPPORTED: <property1> = <value1> AND <property1> = <value2> are multiple key predicates and
//cannot be represented as a resource path.
throw Error.NotSupported(Strings.ALinq_CanOnlyApplyOneKeyPredicate);
}
rse.KeyPredicate.Add(g.Key, g.Value);
}
}
/// <summary>
/// Compares the contents of both collections for equality, ignoring element order.
/// </summary>
/// <typeparam name="T">Type of collection elements.</typeparam>
/// <param name="left">Left-hand side collection.</param>
/// <param name="right">Right-hand side collection.</param>
/// <param name="comparer">Comparer object.</param>
/// <returns>true if both collections contain the same elements; false otherwise.</returns>
private static bool CollectionContentsEqual<T>(ICollection<T> left, ICollection<T> right, IEqualityComparer<T> comparer) where T : class
{
Debug.Assert(left != null, "left != null");
Debug.Assert(right != null, "right != null");
Debug.Assert(comparer != null, "comparer != null");
if (left.Count != right.Count)
{
return false;
}
if (left.Count == 1)
{
return comparer.Equals(left.First(), right.First());
}
else
{
#if ASTORIA_LIGHT // Silverlight doesn't the HashSet constructor with enumerator and comparer.
HashSet<T> leftElements = new HashSet<T>(comparer);
foreach (var l in left)
{
leftElements.Add(l);
}
#else
HashSet<T> leftElements = new HashSet<T>(left, comparer);
#endif
foreach (T rightElement in right)
{
if (!leftElements.Contains(rightElement))
{
return false;
}
}
return true;
}
}
/// <summary>
/// Given a list of predicates, extracts key values for the specified <paramref name="target"/>.
/// </summary>
/// <param name="target">Target set.</param>
/// <param name="predicates">Candidate predicates.</param>
/// <param name="keyValues">Dictionary of values for each property.</param>
/// <returns>true if predicates cover the key.</returns>
private static bool ExtractKeyPredicate(
ResourceSetExpression target,
List<Expression> predicates,
out Dictionary<PropertyInfo, ConstantExpression> keyValues)
{
Debug.Assert(target != null, "target != null");
Debug.Assert(predicates != null, "predicates != null");
keyValues = null;
List<Expression> nonKeyPredicates = null;
foreach (Expression predicate in predicates)
{
PropertyInfo property;
ConstantExpression constantValue;
if (PatternRules.MatchKeyComparison(predicate, out property, out constantValue))
{
if (keyValues == null)
{
keyValues = new Dictionary<PropertyInfo, ConstantExpression>(EqualityComparer<PropertyInfo>.Default);
}
else if (keyValues.ContainsKey(property))
{
// UNSUPPORTED: <property1> = <value1> AND <property1> = <value2> are multiple key predicates and
// cannot be represented as a resource path.
throw Error.NotSupported(Strings.ALinq_CanOnlyApplyOneKeyPredicate);
}
keyValues.Add(property, constantValue);
}
else
{
if (nonKeyPredicates == null)
{
nonKeyPredicates = new List<Expression>();
}
nonKeyPredicates.Add(predicate);
}
}
Debug.Assert(keyValues != null || nonKeyPredicates != null, "No key predicates or non-key predicates found?");
if (keyValues != null)
{
var properties = PatternRules.GetKeyProperties(target.CreateReference().Type);
if (!CollectionContentsEqual(properties, keyValues.Keys, PropertyInfoEqualityComparer.Instance))
{
keyValues = null;
return false;
}
}
// If keyValues is non-null then at least one expression from predicates was a key comparison
// and should no longer be present in the predicates list since it is now in the key values dictionary.
if (keyValues != null)
{
// Remove all predicates.
predicates.Clear();
// If any non-key predicates were found, add them back
if (nonKeyPredicates != null)
{
predicates.AddRange(nonKeyPredicates);
}
}
return keyValues != null;
}
private static Expression BuildKeyPredicateFilter(InputReferenceExpression input, Dictionary<PropertyInfo, ConstantExpression> keyValuesDictionary)
{
Debug.Assert(input != null, "input != null");
Debug.Assert(keyValuesDictionary != null, "keyValuesDictionary != null");
Debug.Assert(keyValuesDictionary.Count > 0, "At least one key property is required in a key predicate");
Expression retExpr = null;
foreach (KeyValuePair<PropertyInfo, ConstantExpression> keyValue in keyValuesDictionary)
{
Expression clause = Expression.Equal(Expression.Property(input, keyValue.Key), keyValue.Value);
if (retExpr == null)
{
retExpr = clause;
}
else
{
retExpr = Expression.And(retExpr, clause);
}
}
return retExpr;
}
/// <summary>Adds all AND'ed expressions to the specified <paramref name="conjuncts"/> list.</summary>
/// <param name="e">Expression to recursively add conjuncts from.</param>
/// <param name="conjuncts">Target list of conjucts.</param>
private static void AddConjuncts(Expression e, List<Expression> conjuncts)
{
Debug.Assert(conjuncts != null, "conjuncts != null");
if (PatternRules.MatchAnd(e))
{
BinaryExpression be = (BinaryExpression)e;
AddConjuncts(be.Left, conjuncts);
AddConjuncts(be.Right, conjuncts);
}
else
{
conjuncts.Add(e);
}
}
/// <summary>
/// Analyzes the specified call to see whether it is recognized as a
/// projection that is satisfied with $select usage.
/// </summary>
/// <param name="mce">Call expression to analyze.</param>
/// <param name="sequenceMethod">Kind of sequence method to analyze.</param>
/// <param name="e">Resulting expression.</param>
/// <returns>true if <paramref name="mce"/> is a projection; false otherwise.</returns>
internal bool AnalyzeProjection(MethodCallExpression mce, SequenceMethod sequenceMethod, out Expression e)
{
Debug.Assert(mce != null, "mce != null");
Debug.Assert(
sequenceMethod == SequenceMethod.Select || sequenceMethod == SequenceMethod.SelectManyResultSelector,
"sequenceMethod == SequenceMethod.Select(ManyResultSelector)");
e = mce;
bool matchMembers = sequenceMethod == SequenceMethod.SelectManyResultSelector;
ResourceExpression source = this.Visit(mce.Arguments[0]) as ResourceExpression;
if (source == null)
{
return false;
}
if (sequenceMethod == SequenceMethod.SelectManyResultSelector)
{
// The processing for SelectMany for a projection is similar to that
// of a regular Select as a projection, however in the latter the
// signature is .Select(source, selector), whereas in the former
// the signature is .SelectMany(source, collector, selector), where
// the selector's source is the result of the collector.
//
// Only simple collectors (single member access) are supported.
Expression collectionSelector = mce.Arguments[1];
if (!PatternRules.MatchParameterMemberAccess(collectionSelector))
{
return false;
}
Expression resultSelector = mce.Arguments[2];
LambdaExpression resultLambda;
if (!PatternRules.MatchDoubleArgumentLambda(resultSelector, out resultLambda))
{
return false;
}
if (ExpressionPresenceVisitor.IsExpressionPresent(resultLambda.Parameters[0], resultLambda.Body))
{
return false;
}
// Build a version of the collection body that has transparent identifiers
// resolved and create a new resource reference for the navigation collection;
// this is the source for the selector.
List<ResourceExpression> referencedExpressions = new List<ResourceExpression>();
LambdaExpression collectionLambda = StripTo<LambdaExpression>(collectionSelector);
Expression collectorReference = InputBinder.Bind(collectionLambda.Body, source, collectionLambda.Parameters[0], referencedExpressions);
collectorReference = StripCastMethodCalls(collectorReference);
MemberExpression navigationMember;
if (!PatternRules.MatchPropertyProjectionSet(source, collectorReference, out navigationMember))
{
return false;
}
collectorReference = navigationMember;
ResourceExpression resultSelectorSource = CreateResourceSetExpression(mce.Method.ReturnType, source, collectorReference, TypeSystem.GetElementType(collectorReference.Type));
if (!PatternRules.MatchMemberInitExpressionWithDefaultConstructor(resultSelectorSource, resultLambda) &&
!PatternRules.MatchNewExpression(resultSelectorSource, resultLambda))
{
return false;
}
#if ASTORIA_LIGHT
resultLambda = ExpressionHelpers.CreateLambda(resultLambda.Body, new ParameterExpression[] { resultLambda.Parameters[1] });
#else
resultLambda = Expression.Lambda(resultLambda.Body, new ParameterExpression[] { resultLambda.Parameters[1] });
#endif
// Ideally, the projection analyzer would return true/false and an
// exception with the explanation of the results if it failed;
// however to minimize churn we will use a try/catch on
// NotSupportedException instead.
ResourceExpression resultWithProjection = resultSelectorSource.CreateCloneWithNewType(mce.Type);
bool isProjection;
try
{
isProjection = ProjectionAnalyzer.Analyze(resultLambda, resultWithProjection, false);
}
catch (NotSupportedException)
{
isProjection = false;
}
if (!isProjection)
{
return false;
}
e = resultWithProjection;
ValidationRules.RequireCanProject(resultSelectorSource);
}
else
{
LambdaExpression lambda;
if (!PatternRules.MatchSingleArgumentLambda(mce.Arguments[1], out lambda))
{
return false;
}
// the projection might be over a transparent identifier, so first try to rewrite if that is the case
lambda = ProjectionRewriter.TryToRewrite(lambda, source.ResourceType);
ResourceExpression re = source.CreateCloneWithNewType(mce.Type);
// See whether the lambda matches a projection that is satisfied with $select usage.
if (!ProjectionAnalyzer.Analyze(lambda, re, matchMembers))
{
return false;
}
// Defer validating until after the projection has been analyzed since the lambda could represent a
// navigation that we do not wat to turn into a projection.
ValidationRules.RequireCanProject(source);
e = re;
}
return true;
}
/// <summary>
/// Analyzes the specified method call as a WCF Data
/// Services navigation operation.
/// </summary>
/// <param name="mce">Expression to analyze.</param>
/// <returns>An expression that represents the potential navigation.</returns>
internal static Expression AnalyzeNavigation(MethodCallExpression mce)
{
Debug.Assert(mce != null, "mce != null");
Expression input = mce.Arguments[0];
LambdaExpression le;
ResourceExpression navSource;
Expression boundProjection;
MemberExpression navigationMember;
if (!PatternRules.MatchSingleArgumentLambda(mce.Arguments[1], out le))
{
return mce;
}
else if (PatternRules.MatchIdentitySelector(le))
{
return input;
}
else if (PatternRules.MatchTransparentIdentitySelector(input, le))
{
return RemoveTransparentScope(mce.Method.ReturnType, (ResourceSetExpression)input);
}
else if (IsValidNavigationSource(input, out navSource) &&
TryBindToInput(navSource, le, out boundProjection) &&
PatternRules.MatchPropertyProjectionSingleton(navSource, boundProjection, out navigationMember))
{
boundProjection = navigationMember;
return CreateNavigationPropertySingletonExpression(mce.Method.ReturnType, navSource, boundProjection);
}
return mce;
}
private static bool IsValidNavigationSource(Expression input, out ResourceExpression sourceExpression)
{
ValidationRules.RequireCanNavigate(input);
sourceExpression = input as ResourceExpression;
return sourceExpression != null;
}
/// <summary>
/// Analyzes a .Select or .SelectMany method call to determine
/// whether it's allowed, to identify transparent identifiers
/// in appropriate .SelectMany() cases, returning the method
/// call or a resource set expression.
/// </summary>
/// <param name="mce">Expression to analyze.</param>
/// <returns>
/// <paramref name="mce"/>, or a new resource set expression for
/// the target resource in the method call for navigation properties.
/// </returns>
internal static Expression AnalyzeSelectMany(MethodCallExpression mce)
{
Debug.Assert(mce != null, "mce != null");
if (mce.Arguments.Count != 2 && mce.Arguments.Count != 3)
{
return mce;
}
ResourceExpression input;
if (!IsValidNavigationSource(mce.Arguments[0], out input))
{
return mce;
}
LambdaExpression collectorSelector;
if (!PatternRules.MatchSingleArgumentLambda(mce.Arguments[1], out collectorSelector))
{
return mce;
}
List<ResourceExpression> referencedInputs = new List<ResourceExpression>();
Expression navPropRef = InputBinder.Bind(collectorSelector.Body, input, collectorSelector.Parameters[0], referencedInputs);
Type resourceType = TypeSystem.GetElementType(navPropRef.Type);
navPropRef = StripCastMethodCalls(navPropRef);
ResourceSetExpression rse = null;
MemberExpression navigationMember;
if (PatternRules.MatchPropertyProjectionSet(input, navPropRef, out navigationMember))
{
navPropRef = navigationMember;
rse = CreateResourceSetExpression(mce.Method.ReturnType, input, navPropRef, resourceType);
}
else
{
return mce;
}
if (mce.Arguments.Count == 3)
{
return AnalyzeSelectManySelector(mce, rse);
}
else
{
return rse;
}
}
/// <summary>
/// Analyzes a SelectMany method call that ranges over a resource set and
/// returns the same method or the annotated resource set.
/// </summary>
/// <param name="selectManyCall">SelectMany method to analyze.</param>
/// <param name="sourceResourceSet">Source resource set for SelectMany result.</param>
/// <returns>The visited expression.</returns>
/// <remarks>
/// The <paramref name="sourceResourceSet"/> expression represents the
/// navigation produced by the collector of the SelectMany() method call.
/// </remarks>
private static Expression AnalyzeSelectManySelector(MethodCallExpression selectManyCall, ResourceSetExpression sourceResourceSet)
{
Debug.Assert(selectManyCall != null, "selectManyCall != null");
LambdaExpression selector = StripTo<LambdaExpression>(selectManyCall.Arguments[2]);
// Check for transparent scope result - projects the input and the selector
Expression result;
ResourceSetExpression.TransparentAccessors transparentScope;
if (PatternRules.MatchTransparentScopeSelector(sourceResourceSet, selector, out transparentScope))
{
sourceResourceSet.TransparentScope = transparentScope;
result = sourceResourceSet;
}
else if (PatternRules.MatchIdentityProjectionResultSelector(selector))
{
result = sourceResourceSet;
}
else if (PatternRules.MatchMemberInitExpressionWithDefaultConstructor(sourceResourceSet, selector) || PatternRules.MatchNewExpression(sourceResourceSet, selector))
{
// Projection analyzer will throw if it selector references first ParamExpression, so this is safe to do here.
#if ASTORIA_LIGHT
selector = ExpressionHelpers.CreateLambda(selector.Body, new ParameterExpression[] { selector.Parameters[1] });
#else
selector = Expression.Lambda(selector.Body, new ParameterExpression[] { selector.Parameters[1] });
#endif
if (!ProjectionAnalyzer.Analyze(selector, sourceResourceSet, false))
{
result = selectManyCall;
}
else
{
result = sourceResourceSet;
}
}
else
{
result = selectManyCall;
}
return result;
}
internal static Expression ApplyOrdering(MethodCallExpression mce, ResourceSetExpression input, Expression selector, bool descending, bool thenBy)
{
List<OrderByQueryOptionExpression.Selector> selectors;
if (!thenBy)
{
selectors = new List<OrderByQueryOptionExpression.Selector>();
AddSequenceQueryOption(input, new OrderByQueryOptionExpression(mce.Type, selectors));
}
else
{
Debug.Assert(input.OrderBy != null, "input.OrderBy != null");
selectors = input.OrderBy.Selectors;
}
selectors.Add(new OrderByQueryOptionExpression.Selector(selector, descending));
return input;
}
#if !ASTORIA_LIGHT // Silverlight doesn't support synchronous operators.
/// <summary>Ensures that there's a limit on the cardinality of a query.</summary>
/// <param name="mce"><see cref="MethodCallExpression"/> for the method to limit First/Single(OrDefault).</param>
/// <param name="maxCardinality">Maximum cardinality to allow.</param>
/// <returns>
/// An expression that limits <paramref name="mce"/> to no more than <paramref name="maxCardinality"/> elements.
/// </returns>
/// <remarks>This method is used by .First(OrDefault) and .Single(OrDefault) to limit cardinality.</remarks>
private static Expression LimitCardinality(MethodCallExpression mce, int maxCardinality)
{
Debug.Assert(mce != null, "mce != null");
Debug.Assert(maxCardinality > 0, "Cardinality must be at least 1");
if (mce.Arguments.Count != 1)
{
// Don't support methods with predicates.
return mce;
}
ResourceSetExpression rse = mce.Arguments[0] as ResourceSetExpression;
if (rse != null)
{
if (!rse.HasKeyPredicate && // no-op, already returns a singleton
(ResourceExpressionType)rse.NodeType != ResourceExpressionType.ResourceNavigationProperty)
{
if (rse.Take == null || (int)rse.Take.TakeAmount.Value > maxCardinality)
{
AddSequenceQueryOption(rse, new TakeQueryOptionExpression(mce.Type, Expression.Constant(maxCardinality)));
}
}
return mce.Arguments[0];
}
else if (mce.Arguments[0] is NavigationPropertySingletonExpression)
{
// no-op, already returns a singleton
return mce.Arguments[0];
}
return mce;
}
#endif
private static Expression AnalyzeCast(MethodCallExpression mce)
{
ResourceExpression re = mce.Arguments[0] as ResourceExpression;
if (re != null)
{
return re.CreateCloneWithNewType(mce.Method.ReturnType);
}
return mce;
}
private static Expression StripConvert(Expression e)
{
UnaryExpression ue = e as UnaryExpression;
// Dev10 Bug# 546646: We are going to allow either of DataServiceQuery or DataServiceOrderedQuery
// to be the type of ResourceExpression in the cast parameter. Although this might be considered
// overly relaxed we want to avoid causing breaking changes by just having the Ordered version
if (ue != null &&
ue.NodeType == ExpressionType.Convert &&
ue.Type.IsGenericType &&
(ue.Type.GetGenericTypeDefinition() == typeof(DataServiceQuery<>) ||
ue.Type.GetGenericTypeDefinition() == typeof(DataServiceQuery<>.DataServiceOrderedQuery)))
{
e = ue.Operand;
ResourceExpression re = e as ResourceExpression;
if (re != null)
{
e = re.CreateCloneWithNewType(ue.Type);
}
}
return e;
}
private static Expression AnalyzeExpand(MethodCallExpression mce)
{
Expression obj = StripConvert(mce.Object);
ResourceExpression re = obj as ResourceExpression;
if (re == null)
{
return mce;
}
ValidationRules.RequireCanExpand(re);
ConstantExpression ce = StripTo<ConstantExpression>(mce.Arguments[0]);
string path = (string)ce.Value;
if (!re.ExpandPaths.Contains(path))
{
re.ExpandPaths.Add(path);
}
return re;
}
private static Expression AnalyzeAddCustomQueryOption(MethodCallExpression mce)
{
Expression obj = StripConvert(mce.Object);
ResourceExpression re = obj as ResourceExpression;
if (re == null)
{
return mce;
}
ValidationRules.RequireCanAddCustomQueryOption(re);
ConstantExpression name = StripTo<ConstantExpression>(mce.Arguments[0]);
ConstantExpression value = StripTo<ConstantExpression>(mce.Arguments[1]);
if (((string)name.Value).Trim() == UriHelper.DOLLARSIGN + UriHelper.OPTIONEXPAND)
{
// if the user is setting $expand option, need to merge with other existing expand paths that may have been alredy added
// check for allow expansion
ValidationRules.RequireCanExpand(re);
re.ExpandPaths = re.ExpandPaths.Union(new string[] { (string) value.Value }, StringComparer.Ordinal).ToList();
}
else
{
ValidationRules.RequireLegalCustomQueryOption(mce.Arguments[0], re);
re.CustomQueryOptions.Add(name, value);
}
return re;
}
private static Expression AnalyzeAddCountOption(MethodCallExpression mce, CountOption countOption)
{
Expression obj = StripConvert(mce.Object);
ResourceExpression re = obj as ResourceExpression;
if (re == null)
{
return mce;
}
ValidationRules.RequireCanAddCount(re);
ValidationRules.RequireNonSingleton(re);
re.CountOption = countOption;
return re;
}
/// <summary>Creates a new resource set as produced by a navigation.</summary>
/// <param name="type">
/// The type of the expression as it appears in the tree (possibly
/// with transparent scopes).
/// </param>
/// <param name="source">The source of the set.</param>
/// <param name="memberExpression">The member access on <paramref name="source"/> that yields the set.</param>
/// <param name="resourceType">The resource type on the set.</param>
/// <returns>A new <see cref="ResourceSetExpression"/> instance.</returns>
private static ResourceSetExpression CreateResourceSetExpression(Type type, ResourceExpression source, Expression memberExpression, Type resourceType)
{
Debug.Assert(type != null, "type != null");
Debug.Assert(source != null, "source != null");
Debug.Assert(memberExpression != null, "memberExpression != null");
Debug.Assert(resourceType != null, "resourceType != null");
// ResourceSetExpressions can always have order information,
// so return them as IOrderedQueryable<> always. Necessary to allow
// OrderBy results that get aliased to a previous expression work
// with ThenBy.
Type elementType = TypeSystem.GetElementType(type);
Debug.Assert(elementType != null, "elementType != null -- otherwise the set isn't going to act like a collection");
Type expressionType = typeof(IOrderedQueryable<>).MakeGenericType(elementType);
ResourceSetExpression newResource = new ResourceSetExpression(expressionType, source, memberExpression, resourceType, source.ExpandPaths.ToList(), source.CountOption, source.CustomQueryOptions.ToDictionary(kvp => kvp.Key, kvp => kvp.Value), null);
source.ExpandPaths.Clear();
source.CountOption = CountOption.None;
source.CustomQueryOptions.Clear();
return newResource;
}
private static NavigationPropertySingletonExpression CreateNavigationPropertySingletonExpression(Type type, ResourceExpression source, Expression memberExpression)
{
NavigationPropertySingletonExpression newResource = new NavigationPropertySingletonExpression(type, source, memberExpression, memberExpression.Type, source.ExpandPaths.ToList(), source.CountOption, source.CustomQueryOptions.ToDictionary(kvp => kvp.Key, kvp => kvp.Value), null);
source.ExpandPaths.Clear();
source.CountOption = CountOption.None;
source.CustomQueryOptions.Clear();
return newResource;
}
/// <summary>
/// Produces a new <see cref="ResourceSetExpression"/> that is a clone of <paramref name="input"/> in all respects,
/// other than its result type - which will be <paramref name="expectedResultType"/> - and its transparent scope,
/// which will be <c>null</c>. This is a shallow clone operation - sequence query options, key predicate, etc are
/// not cloned, but are reassigned to the new instance. The <paramref name="input"/> resource expression should be
/// discarded after being used with this method.
/// </summary>
/// <param name="expectedResultType">The result type - <see cref="Expression.Type"/> - that the new resource set expression should have.</param>
/// <param name="input">The resource set expression from which the transparent scope is being removed</param>
/// <returns>A new resource set expression without an enclosing transparent scope and with the specified result type.</returns>
private static ResourceSetExpression RemoveTransparentScope(Type expectedResultType, ResourceSetExpression input)
{
// Create a new resource set expression based on the input
ResourceSetExpression newResource = new ResourceSetExpression(expectedResultType, input.Source, input.MemberExpression, input.ResourceType, input.ExpandPaths, input.CountOption, input.CustomQueryOptions, input.Projection);
// Reassign state items that are not constructor arguments - query options + key predicate
newResource.KeyPredicate = input.KeyPredicate;
foreach (QueryOptionExpression queryOption in input.SequenceQueryOptions)
{
newResource.AddSequenceQueryOption(queryOption);
}
// Instruct the new resource set expression to use input's Input Reference instead of creating its own.
// This will also update the Input Reference to specify the new resource set expression as it's target,
// so that any usage of it in query option expressions is consistent with the new resource set expression.
newResource.OverrideInputReference(input);
return newResource;
}
/// <summary>Returns the specified expression, stripping redundant converts.</summary>
/// <param name="e">Expression to return.</param>
/// <returns>e, or the underlying expression for redundant converts.</returns>
internal static Expression StripConvertToAssignable(Expression e)
{
Debug.Assert(e != null, "e != null");
Expression result;
UnaryExpression unary = e as UnaryExpression;
if (unary != null && PatternRules.MatchConvertToAssignable(unary))
{
result = unary.Operand;
}
else
{
result = e;
}
return result;
}
/// <summary>
/// Strips the specifed <paramref name="expression"/> of intermediate
/// expression (unnecessary converts and quotes) and returns
/// the underlying expression of type T (or null if it's not of that type).
/// </summary>
/// <typeparam name="T">Type of expression to return.</typeparam>
/// <param name="expression">Expression to consider.</param>
/// <returns>The underlying expression for <paramref name="expression"/>.</returns>
internal static T StripTo<T>(Expression expression) where T : Expression
{
Debug.Assert(expression != null, "expression != null");
Expression result;
do
{
result = expression;
expression = expression.NodeType == ExpressionType.Quote ? ((UnaryExpression)expression).Operand : expression;
expression = StripConvertToAssignable(expression);
}
while (result != expression);
return result as T;
}
internal override Expression VisitResourceSetExpression(ResourceSetExpression rse)
{
Debug.Assert(rse != null, "rse != null");
if ((ResourceExpressionType)rse.NodeType == ResourceExpressionType.RootResourceSet)
{
// Actually, the user may already have composed an expansion, so
// we'll find a query option here.
// Debug.Assert(!rse.HasQueryOptions, "!rse.HasQueryOptions");
// since we could be adding query options to the root, create a new one which can be mutable.
return new ResourceSetExpression(rse.Type, rse.Source, rse.MemberExpression, rse.ResourceType, null, CountOption.None, null, null);
}
return rse;
}
private static bool TryGetResourceSetMethodArguments(MethodCallExpression mce, out ResourceSetExpression input, out LambdaExpression lambda)
{
input = null;
lambda = null;
input = mce.Arguments[0] as ResourceSetExpression;
if (input != null &&
PatternRules.MatchSingleArgumentLambda(mce.Arguments[1], out lambda))
{
return true;
}
return false;
}
private static bool TryBindToInput(ResourceExpression input, LambdaExpression le, out Expression bound)
{
List<ResourceExpression> referencedInputs = new List<ResourceExpression>();
bound = InputBinder.Bind(le.Body, input, le.Parameters[0], referencedInputs);
if (referencedInputs.Count > 1 || (referencedInputs.Count == 1 && referencedInputs[0] != input))
{
bound = null;
}
return bound != null;
}
private static Expression AnalyzeResourceSetMethod(MethodCallExpression mce, Func<MethodCallExpression, ResourceSetExpression, Expression, Expression> sequenceMethodAnalyzer)
{
ResourceSetExpression input;
LambdaExpression le;
if (!TryGetResourceSetMethodArguments(mce, out input, out le))
{
// UNSUPPORTED: Expected LambdaExpression as second argument to sequence method
return mce;
}
Expression lambdaBody;
if (!TryBindToInput(input, le, out lambdaBody))
{
// UNSUPPORTED: Lambda should reference the input, and only the input
return mce;
}
return sequenceMethodAnalyzer(mce, input, lambdaBody);
}
private static Expression AnalyzeResourceSetConstantMethod(MethodCallExpression mce, Func<MethodCallExpression, ResourceExpression, ConstantExpression, Expression> constantMethodAnalyzer)
{
ResourceExpression input = (ResourceExpression)mce.Arguments[0];
ConstantExpression constantArg = StripTo<ConstantExpression>(mce.Arguments[1]);
if (null == constantArg)
{
// UNSUPPORTED: A ConstantExpression is expected
return mce;
}
return constantMethodAnalyzer(mce, input, constantArg);
}
private static Expression AnalyzeCountMethod(MethodCallExpression mce)
{
// [Res].LongCount()
// [Res].Count()
ResourceExpression re = (ResourceExpression)mce.Arguments[0];
if (re == null)
{
return mce;
}
ValidationRules.RequireCanAddCount(re);
ValidationRules.RequireNonSingleton(re);
re.CountOption = CountOption.ValueOnly;
return re;
}
private static void AddSequenceQueryOption(ResourceExpression target, QueryOptionExpression qoe)
{
ValidationRules.RequireNonSingleton(target);
ResourceSetExpression rse = (ResourceSetExpression)target;
// Validation that can add option
switch (qoe.NodeType)
{
case (ExpressionType)ResourceExpressionType.FilterQueryOption:
if (rse.Skip != null)
{
throw new NotSupportedException(Strings.ALinq_QueryOptionOutOfOrder("filter", "skip"));
}
else if (rse.Take != null)
{
throw new NotSupportedException(Strings.ALinq_QueryOptionOutOfOrder("filter", "top"));
}
else if (rse.Projection != null)
{
throw new NotSupportedException(Strings.ALinq_QueryOptionOutOfOrder("filter", "select"));
}
break;
case (ExpressionType)ResourceExpressionType.OrderByQueryOption:
if (rse.Skip != null)
{
throw new NotSupportedException(Strings.ALinq_QueryOptionOutOfOrder("orderby", "skip"));
}
else if (rse.Take != null)
{
throw new NotSupportedException(Strings.ALinq_QueryOptionOutOfOrder("orderby", "top"));
}
else if (rse.Projection != null)
{
throw new NotSupportedException(Strings.ALinq_QueryOptionOutOfOrder("orderby", "select"));
}
break;
case (ExpressionType)ResourceExpressionType.SkipQueryOption:
if (rse.Take != null)
{
throw new NotSupportedException(Strings.ALinq_QueryOptionOutOfOrder("skip", "top"));
}
break;
default:
break;
}
rse.AddSequenceQueryOption(qoe);
}
internal override Expression VisitBinary(BinaryExpression b)
{
Expression e = base.VisitBinary(b);
if (PatternRules.MatchStringAddition(e))
{
BinaryExpression be = StripTo<BinaryExpression>(e);
MethodInfo mi = typeof(string).GetMethod("Concat", new Type[] { typeof(string), typeof(string)});
return Expression.Call(mi, new Expression[] {be.Left, be.Right});
}
return e;
}
internal override Expression VisitMemberAccess(MemberExpression m)
{
Expression e = base.VisitMemberAccess(m);
MemberExpression me = StripTo<MemberExpression>(e);
PropertyInfo pi;
MethodInfo mi;
if (me != null &&
PatternRules.MatchNonPrivateReadableProperty(me, out pi) &&
TypeSystem.TryGetPropertyAsMethod(pi, out mi))
{
return Expression.Call(me.Expression, mi);
}
return e;
}
internal override Expression VisitMethodCall(MethodCallExpression mce)
{
Expression e;
// check first to see if projection (not a navigation) so that func does recursively analyze selector
// Currently the patterns being looked for in the selector are mutually exclusive from naviagtion patterns looked at later.
SequenceMethod sequenceMethod;
if (ReflectionUtil.TryIdentifySequenceMethod(mce.Method, out sequenceMethod))
{
// The leaf projection can be one of Select(source, selector) or
// SelectMany(source, collectionSelector, resultSelector).
if (sequenceMethod == SequenceMethod.Select ||
sequenceMethod == SequenceMethod.SelectManyResultSelector)
{
if (this.AnalyzeProjection(mce, sequenceMethod, out e))
{
return e;
}
}
}
e = base.VisitMethodCall(mce);
mce = e as MethodCallExpression;
if (mce != null)
{
if (ReflectionUtil.TryIdentifySequenceMethod(mce.Method, out sequenceMethod))
{
switch (sequenceMethod)
{
case SequenceMethod.Where:
return AnalyzePredicate(mce);
case SequenceMethod.Select:
return AnalyzeNavigation(mce);
case SequenceMethod.SelectMany:
case SequenceMethod.SelectManyResultSelector:
{
Expression result = AnalyzeSelectMany(mce);
return result;
}
case SequenceMethod.Take:
return AnalyzeResourceSetConstantMethod(mce, (callExp, resource, takeCount) => { AddSequenceQueryOption(resource, new TakeQueryOptionExpression(callExp.Type, takeCount)); return resource; });
case SequenceMethod.Skip:
return AnalyzeResourceSetConstantMethod(mce, (callExp, resource, skipCount) => { AddSequenceQueryOption(resource, new SkipQueryOptionExpression(callExp.Type, skipCount)); return resource; });
case SequenceMethod.OrderBy:
return AnalyzeResourceSetMethod(mce, (callExp, resource, selector) => ApplyOrdering(callExp, resource, selector, /*descending=*/false, /*thenBy=*/false));
case SequenceMethod.ThenBy:
return AnalyzeResourceSetMethod(mce, (callExp, resource, selector) => ApplyOrdering(callExp, resource, selector, /*descending=*/false, /*thenBy=*/true));
case SequenceMethod.OrderByDescending:
return AnalyzeResourceSetMethod(mce, (callExp, resource, selector) => ApplyOrdering(callExp, resource, selector, /*descending=*/true, /*thenBy=*/false));
case SequenceMethod.ThenByDescending:
return AnalyzeResourceSetMethod(mce, (callExp, resource, selector) => ApplyOrdering(callExp, resource, selector, /*descending=*/true, /*thenBy=*/true));
#if !ASTORIA_LIGHT // Silverlight doesn't support synchronous operators.
case SequenceMethod.First:
case SequenceMethod.FirstOrDefault:
return LimitCardinality(mce, 1);
case SequenceMethod.Single:
case SequenceMethod.SingleOrDefault:
return LimitCardinality(mce, 2);
#endif
case SequenceMethod.Cast:
return AnalyzeCast(mce);
case SequenceMethod.LongCount:
case SequenceMethod.Count:
return AnalyzeCountMethod(mce);
default:
throw Error.MethodNotSupported(mce);
}
}
else if (mce.Method.DeclaringType.IsGenericType &&
mce.Method.DeclaringType.GetGenericTypeDefinition() == typeof(DataServiceQuery<>))
{
Type t = typeof(DataServiceQuery<>).MakeGenericType(mce.Method.DeclaringType.GetGenericArguments()[0]);
if (mce.Method == t.GetMethod("Expand", new Type[] { typeof(string) }))
{
return AnalyzeExpand(mce);
}
else if (mce.Method == t.GetMethod("AddQueryOption", new Type[] { typeof(string), typeof(object) }))
{
return AnalyzeAddCustomQueryOption(mce);
}
else if (mce.Method == t.GetMethod("IncludeTotalCount"))
{
return AnalyzeAddCountOption(mce, CountOption.InlineAll);
}
else
{
throw Error.MethodNotSupported(mce);
}
}
return mce;
}
return e;
}
/// <summary>Strips calls to .Cast() methods, returning the underlying expression.</summary>
/// <param name="expression">Expression to strip calls from.</param>
/// <returns>The underlying expression.</returns>
/// <remarks>
/// Note that this method drops information about what the casts were,
/// and is only supported for collector selectors in SelectMany() calls,
/// to enable scenarios such as from t in ctx.Tables from Customer c in t.Items...
/// </remarks>
private static Expression StripCastMethodCalls(Expression expression)
{
Debug.Assert(expression != null, "expression != null");
MethodCallExpression call = StripTo<MethodCallExpression>(expression);
while (call != null && ReflectionUtil.IsSequenceMethod(call.Method, SequenceMethod.Cast))
{
expression = call.Arguments[0];
call = StripTo<MethodCallExpression>(expression);
}
return expression;
}
/// <summary>Use this class to perform pattern-matching over expression trees.</summary>
/// <remarks>
/// Following these guidelines simplifies usage:
///
/// - Return true/false for matches, and interesting matched information in out parameters.
///
/// - If one of the inputs to be matched undergoes "skipping" for unnecesary converts,
/// return the same member as an out parameter. This forces callers to be aware that
/// they should use the more precise representation for computation (without having
/// to rely on a normalization step).
/// </remarks>
internal static class PatternRules
{
/// <summary>
/// Checks whether the <paramref name="expression"/> is a convert that
/// always succeds because it converts to the same target type or a
/// base type.
/// </summary>
/// <param name="expression">Expression to match.</param>
/// <returns>
/// true if <paramref name="expression"/> is a conver to same or base type; false otherwise.
/// </returns>
internal static bool MatchConvertToAssignable(UnaryExpression expression)
{
Debug.Assert(expression != null, "expression != null");
if (expression.NodeType != ExpressionType.Convert &&
expression.NodeType != ExpressionType.ConvertChecked &&
expression.NodeType != ExpressionType.TypeAs)
{
return false;
}
return expression.Type.IsAssignableFrom(expression.Operand.Type);
}
/// <summary>
/// Checks whether <paramref name="expression"/> is a lambda of the
/// form (p) => p.member[.member]* (possibly quoted).
/// </summary>
/// <param name="expression">Expression to match.</param>
/// <returns>true if the expression is a match; false otherwise.</returns>
/// <remarks>
/// This method strip .Call methods because it's currently used only
/// to supporte .SelectMany() collector selectors. If this method
/// is reused for other purposes, this behavior should be made
/// conditional or factored out.
/// </remarks>
internal static bool MatchParameterMemberAccess(Expression expression)
{
Debug.Assert(expression != null, "lambda != null");
LambdaExpression lambda = StripTo<LambdaExpression>(expression);
if (lambda == null || lambda.Parameters.Count != 1)
{
return false;
}
ParameterExpression parameter = lambda.Parameters[0];
Expression body = StripCastMethodCalls(lambda.Body);
MemberExpression memberAccess = StripTo<MemberExpression>(body);
while (memberAccess != null)
{
if (memberAccess.Expression == parameter)
{
return true;
}
memberAccess = StripTo<MemberExpression>(memberAccess.Expression);
}
return false;
}
/// <summary>
/// Checks whether the specified expression is a path of member
/// access expressions.
/// </summary>
/// <param name="e">Expression to match.</param>
/// <param name="member">Expression equivalent to <paramref name="e"/>, without additional converts.</param>
/// <param name="instance">Expression from which the path starts.</param>
/// <param name="propertyPath">Path of member names from <paramref name="instance"/>.</param>
/// <returns>true if there is at least one property in the path; false otherwise.</returns>
internal static bool MatchPropertyAccess(Expression e, out MemberExpression member, out Expression instance, out List<string> propertyPath)
{
instance = null;
propertyPath = null;
MemberExpression me = StripTo<MemberExpression>(e);
member = me;
while (me != null)
{
PropertyInfo pi;
if (MatchNonPrivateReadableProperty(me, out pi))
{
if (propertyPath == null)
{
propertyPath = new List<string>();
}
propertyPath.Insert(0, pi.Name);
e = me.Expression;
me = StripTo<MemberExpression>(e);
}
else
{
me = null;
}
}
if (propertyPath != null)
{
instance = e;
return true;
}
return false;
}
// is constant
internal static bool MatchConstant(Expression e, out ConstantExpression constExpr)
{
constExpr = e as ConstantExpression;
return constExpr != null;
}
internal static bool MatchAnd(Expression e)
{
BinaryExpression be = e as BinaryExpression;
return (be != null && (be.NodeType == ExpressionType.And || be.NodeType == ExpressionType.AndAlso));
}
internal static bool MatchNonPrivateReadableProperty(Expression e, out PropertyInfo propInfo)
{
MemberExpression me = e as MemberExpression;
if (me == null)
{
propInfo = null;
return false;
}
return MatchNonPrivateReadableProperty(me, out propInfo);
}
/// <summary>
/// Checks whether the specified member expression refers to a member
/// that is a non-private property (readable and with getter and/or setter).
/// </summary>
/// <param name="me">Member expression to check.</param>
/// <param name="propInfo">Non-null property info when result is true.</param>
/// <returns>Whether the member refers to a non-private, readable property.</returns>
internal static bool MatchNonPrivateReadableProperty(MemberExpression me, out PropertyInfo propInfo)
{
Debug.Assert(me != null, "me != null");
propInfo = null;
if (me.Member.MemberType == MemberTypes.Property)
{
PropertyInfo pi = (PropertyInfo)me.Member;
if (pi.CanRead && !TypeSystem.IsPrivate(pi))
{
propInfo = pi;
return true;
}
}
return false;
}
/// <summary>
/// Checks whether the specified <paramref name="expression"/> is a member access to a key.
/// </summary>
/// <param name="expression">Expression to check.</param>
/// <param name="property">If this is a key access, the property for the key.</param>
/// <returns>true if <paramref name="expression"/> is a member access to a key; false otherwise.</returns>
internal static bool MatchKeyProperty(Expression expression, out PropertyInfo property)
{
property = null;
// make sure member is property, it is public and has a Getter.
PropertyInfo pi;
if (!PatternRules.MatchNonPrivateReadableProperty(expression, out pi))
{
return false;
}
if (GetKeyProperties(pi.ReflectedType).Contains(pi, PropertyInfoEqualityComparer.Instance))
{
property = pi;
return true;
}
return false;
}
/// <summary>Gets the key properties from the specified <paramref name="type"/>.</summary>
/// <param name="type">Type to get properties from.</param>
/// <returns>A list of properties that are key for the type; possibly an empty list.</returns>
internal static List<PropertyInfo> GetKeyProperties(Type type)
{
Debug.Assert(type != null, "type != null");
ClientType clientType = ClientType.Create(type, false /* expectModelType */);
var result = new List<PropertyInfo>();
foreach (var property in clientType.Properties)
{
if (property.KeyProperty)
{
result.Add(property.DeclaringType.GetProperty(property.PropertyName));
}
}
return result;
}
internal static bool MatchKeyComparison(Expression e, out PropertyInfo keyProperty, out ConstantExpression keyValue)
{
if (PatternRules.MatchBinaryEquality(e))
{
BinaryExpression be = (BinaryExpression)e;
if ((PatternRules.MatchKeyProperty(be.Left, out keyProperty) && PatternRules.MatchConstant(be.Right, out keyValue)) ||
(PatternRules.MatchKeyProperty(be.Right, out keyProperty) && PatternRules.MatchConstant(be.Left, out keyValue)))
{
// if property is compared to null, expression is not key predicate comparison
return keyValue.Value != null;
}
}
keyProperty = null;
keyValue = null;
return false;
}
/// <summary>
/// Checks whether the specified <paramref name="expression"/> matches
/// a call to System.Object.ReferenceEquals.
/// </summary>
/// <param name="expression">Expression to check.</param>
/// <returns>true if the expression matches; false otherwise.</returns>
internal static bool MatchReferenceEquals(Expression expression)
{
Debug.Assert(expression != null, "expression != null");
MethodCallExpression call = expression as MethodCallExpression;
if (call == null)
{
return false;
}
return call.Method == typeof(object).GetMethod("ReferenceEquals");
}
/// <summary>
/// Checks whether the specifed <paramref name="expression"/> refers to a resource.
/// </summary>
/// <param name="expression">Expression to check.</param>
/// <param name="resource">Resource expression if successful.</param>
/// <returns>true if the expression is a resource expression; false otherwise.</returns>
internal static bool MatchResource(Expression expression, out ResourceExpression resource)
{
resource = expression as ResourceExpression;
return resource != null;
}
/// <summary>
/// Checks whether the specified expression is a lambda with a two parameters
/// (possibly quoted).
/// </summary>
/// <param name="expression">Expression to match.</param>
/// <param name="lambda">If the expression matches, the lambda with the two parameters.</param>
/// <returns>true if the expression is a lambda with two parameters.</returns>
internal static bool MatchDoubleArgumentLambda(Expression expression, out LambdaExpression lambda)
{
return MatchNaryLambda(expression, 2, out lambda);
}
/// <summary>
/// Checks whether the specified <paramref name="lambda"/> is a selector
/// of the form (p) => p.
/// </summary>
/// <param name="lambda">Expression to check.</param>
/// <returns>true if the lambda is an identity selector; false otherwise.</returns>
internal static bool MatchIdentitySelector(LambdaExpression lambda)
{
Debug.Assert(lambda != null, "lambda != null");
ParameterExpression parameter = lambda.Parameters[0];
return parameter == StripTo<ParameterExpression>(lambda.Body);
}
/// <summary>
/// Checks whether the specified expression is a lambda with a single parameter
/// (possibly quoted).
/// </summary>
/// <param name="expression">Expression to match.</param>
/// <param name="lambda">If the expression matches, the lambda with the single parameter.</param>
/// <returns>true if the expression is a lambda with a single argument.</returns>
internal static bool MatchSingleArgumentLambda(Expression expression, out LambdaExpression lambda)
{
return MatchNaryLambda(expression, 1, out lambda);
}
/// <summary>
/// Checked whether the specified <paramref name="selector"/> has the
/// form [input's transparent scope].[accessor].
/// </summary>
/// <param name="input">Input expression (source) for the selector.</param>
/// <param name="selector">Selector lambda.</param>
/// <returns>true if the selector's body looks like [input's transparent scope].[accesor].</returns>
internal static bool MatchTransparentIdentitySelector(Expression input, LambdaExpression selector)
{
if (selector.Parameters.Count != 1)
{
return false;
}
ResourceSetExpression rse = input as ResourceSetExpression;
if (rse == null || rse.TransparentScope == null)
{
return false;
}
Expression potentialRef = selector.Body;
ParameterExpression expectedTarget = selector.Parameters[0];
MemberExpression propertyMember;
Expression paramRef;
List<string> refPath;
if (!MatchPropertyAccess(potentialRef, out propertyMember, out paramRef, out refPath))
{
return false;
}
Debug.Assert(refPath != null, "refPath != null -- otherwise MatchPropertyAccess should not have returned true");
return paramRef == expectedTarget && refPath.Count == 1 && refPath[0] == rse.TransparentScope.Accessor;
}
internal static bool MatchIdentityProjectionResultSelector(Expression e)
{
LambdaExpression le = (LambdaExpression)e;
return (le.Body == le.Parameters[1]);
}
/// <summary>
/// Checks wheter the specified lambda matches a selector that yields
/// a transparent identifier.
/// </summary>
/// <param name="input">
/// The input expression for the lambda, used to set up the
/// references from the transparent scope if one is produced.
/// </param>
/// <param name="resultSelector">Lambda expression to match.</param>
/// <param name="transparentScope">
/// After invocation, information on the accessors if the result
/// is true; null otherwise.
/// </param>
/// <returns>
/// true if <paramref name="input"/> is a selector for a transparent
/// identifier; false otherwise.
/// </returns>
/// <remarks>
/// Note that C# and VB.NET have different patterns for accumulating
/// parameters.
///
/// C# uses a two-member anonymous type with "everything so far"
/// plus the newly introduced range variable.
///
/// VB.NET uses an n-member anonymous type by pulling range variables
/// from a previous anonymous type (or the first range variable),
/// plus the newly introduced range variable.
///
/// For additional background, see:
/// Transparent Identifiers - http://blogs.msdn.com/Microsoft/archive/2006/12/22/transparent-identifiers.aspx
/// http://msdn.microsoft.com/en-us/library/bb308966.aspx
/// In particular:
/// - 26.7.1.4 From, let, where, join and orderby clauses
/// - 26.7.1.7 Transparent identifiers
///
/// <paramref name="input"/> is the expression that represents the
/// navigation resulting from the collector selector in the
/// SelectMany() call under analysis.
/// </remarks>
internal static bool MatchTransparentScopeSelector(ResourceSetExpression input, LambdaExpression resultSelector, out ResourceSetExpression.TransparentAccessors transparentScope)
{
transparentScope = null;
// Constructing transparent identifiers must be a simple instantiation.
if (resultSelector.Body.NodeType != ExpressionType.New)
{
return false;
}
// Less than two arguments implies there's no new range variable introduced.
NewExpression ne = (NewExpression)resultSelector.Body;
if (ne.Arguments.Count < 2)
{
return false;
}
// Transparent identifier must not be part of hierarchies.
if (ne.Type.BaseType != typeof(object))
{
return false;
}
// Transparent identifiers have a public property per constructor,
// matching the parameter name.
ParameterInfo[] constructorParams = ne.Constructor.GetParameters();
if (ne.Members.Count != constructorParams.Length)
{
return false;
}
// Every argument to the constructor should be a lambdaparameter or
// a member access off one. The introduced range variable is always
// a standalone parameter (note that for the first transparent
// identifier, both are; we pick it by convention in that case).
ResourceSetExpression inputSourceSet = input.Source as ResourceSetExpression;
int introducedMemberIndex = -1;
ParameterExpression collectorSourceParameter = resultSelector.Parameters[0];
ParameterExpression introducedRangeParameter = resultSelector.Parameters[1];
MemberInfo[] memberProperties = new MemberInfo[ne.Members.Count];
PropertyInfo[] properties = ne.Type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
Dictionary<string, Expression> sourceAccessors = new Dictionary<string, Expression>(constructorParams.Length - 1, StringComparer.Ordinal);
for (int i = 0; i < ne.Arguments.Count; i++)
{
Expression argument = ne.Arguments[i];
MemberInfo member = ne.Members[i];
if (!ExpressionIsSimpleAccess(argument, resultSelector.Parameters))
{
return false;
}
// Transparent identifiers have a property that matches the parameter
// name. The Members collection contains the get_Foo methods.
if (member.MemberType == MemberTypes.Method)
{
member = properties.Where(property => property.GetGetMethod() == member).FirstOrDefault();
if (member == null)
{
return false;
}
}
if (member.Name != constructorParams[i].Name)
{
return false;
}
memberProperties[i] = member;
ParameterExpression argumentAsParameter = StripTo<ParameterExpression>(argument);
if (introducedRangeParameter == argumentAsParameter)
{
if (introducedMemberIndex != -1)
{
return false;
}
introducedMemberIndex = i;
}
else if (collectorSourceParameter == argumentAsParameter)
{
sourceAccessors[member.Name] = inputSourceSet.CreateReference();
}
else
{
List<ResourceExpression> referencedInputs = new List<ResourceExpression>();
Expression boundArgument = InputBinder.Bind(argument, inputSourceSet, resultSelector.Parameters[0], referencedInputs);
if (referencedInputs.Count != 1)
{
return false;
}
sourceAccessors[member.Name] = referencedInputs[0].CreateReference();
}
}
// Transparent identifers should add at least one new range variable.
if (introducedMemberIndex == -1)
{
return false;
}
string resultAccessor = memberProperties[introducedMemberIndex].Name;
transparentScope = new ResourceSetExpression.TransparentAccessors(resultAccessor, sourceAccessors);
return true;
}
/// <summary>
/// Checks whether the specified <paramref name="input"/> is a member access
/// that references <paramref name="potentialPropertyRef"/>.
/// </summary>
/// <param name="input">Expression to check.</param>
/// <param name="potentialPropertyRef">InputReferenceExpression to consider as source.</param>
/// <param name="navigationMember">Navigation member, equivalent to <paramref name="potentialPropertyRef"/> without unnecessary casts.</param>
/// <returns>
/// true if <paramref name="input"/> is a property collection that originates in
/// <paramref name="potentialPropertyRef"/>; false otherwise.
/// </returns>
internal static bool MatchPropertyProjectionSet(ResourceExpression input, Expression potentialPropertyRef, out MemberExpression navigationMember)
{
return MatchNavigationPropertyProjection(input, potentialPropertyRef, true, out navigationMember);
}
/// <summary>
/// Checks whether the specified <paramref name="input"/> is a member access
/// that references <paramref name="potentialPropertyRef"/>.
/// </summary>
/// <param name="input">Expression to check.</param>
/// <param name="potentialPropertyRef">InputReferenceExpression to consider as source.</param>
/// <param name="navigationMember">Navigation member, equivalent to <paramref name="potentialPropertyRef"/> without unnecessary casts.</param>
/// <returns>
/// true if <paramref name="input"/> is a property navigation (singleton) that originates in
/// <paramref name="potentialPropertyRef"/>; false otherwise.
/// </returns>
internal static bool MatchPropertyProjectionSingleton(ResourceExpression input, Expression potentialPropertyRef, out MemberExpression navigationMember)
{
return MatchNavigationPropertyProjection(input, potentialPropertyRef, false, out navigationMember);
}
/// <summary>
/// Checks whether the specified <paramref name="input"/> is a member access
/// that references <paramref name="potentialPropertyRef"/>.
/// </summary>
/// <param name="input">Expression to check.</param>
/// <param name="potentialPropertyRef">InputReferenceExpression to consider as source.</param>
/// <param name="requireSet">Whether the match should be for a set or a singleton navigation property.</param>
/// <param name="navigationMember">Navigation member, equivalent to <paramref name="potentialPropertyRef"/> without unnecessary casts.</param>
/// <returns>
/// true if <paramref name="input"/> is a property that originates in
/// <paramref name="potentialPropertyRef"/>; false otherwise.
/// </returns>
private static bool MatchNavigationPropertyProjection(ResourceExpression input, Expression potentialPropertyRef, bool requireSet, out MemberExpression navigationMember)
{
if (PatternRules.MatchNonSingletonProperty(potentialPropertyRef) == requireSet)
{
Expression foundInstance;
List<string> propertyNames;
if (MatchPropertyAccess(potentialPropertyRef, out navigationMember, out foundInstance, out propertyNames))
{
if (foundInstance == input.CreateReference())
{
return true;
}
}
}
navigationMember = null;
return false;
}
internal static bool MatchMemberInitExpressionWithDefaultConstructor(Expression source, LambdaExpression e)
{
MemberInitExpression mie = StripTo<MemberInitExpression>(e.Body);
ResourceExpression resource;
return MatchResource(source, out resource) && (mie != null) && (mie.NewExpression.Arguments.Count == 0);
}
internal static bool MatchNewExpression(Expression source, LambdaExpression e)
{
ResourceExpression resource;
return MatchResource(source, out resource) && (e.Body is NewExpression);
}
/// <summary>
/// Checks whether <paramref name="expression"/> is a logical negation
/// expression.
/// </summary>
/// <param name="expression">Expression to check.</param>
/// <returns>true if expression is a Not expression; false otherwise.</returns>
internal static bool MatchNot(Expression expression)
{
Debug.Assert(expression != null, "expression != null");
return expression.NodeType == ExpressionType.Not;
}
/// <summary>Checks whether the specified expression is an enumeration.</summary>
/// <param name="e">Expression to check.</param>
/// <returns>true if the type of the expression is an enumeration; false otherwise.</returns>
internal static bool MatchNonSingletonProperty(Expression e)
{
// byte[] and char[] ares ok - other IEnums are not.
return (TypeSystem.FindIEnumerable(e.Type) != null) &&
e.Type != typeof(char[]) &&
e.Type != typeof(byte[]);
}
/// <summary>
/// Checks whether <paramref name="entityInScope"/> is a conditional expression
/// that checks whether a navigation property (reference or collection) is
/// null before proceeding.
/// </summary>
/// <param name="entityInScope">Entity in scope to be checked.</param>
/// <param name="conditional">Expression to check.</param>
/// <returns>Check results.</returns>
internal static MatchNullCheckResult MatchNullCheck(Expression entityInScope, ConditionalExpression conditional)
{
Debug.Assert(conditional != null, "conditional != null");
MatchNullCheckResult result = new MatchNullCheckResult();
MatchEqualityCheckResult equalityCheck = MatchEquality(conditional.Test);
if (!equalityCheck.Match)
{
return result;
}
Expression assignedCandidate;
if (equalityCheck.EqualityYieldsTrue)
{
// Pattern: memberCandidate EQ null ? null : memberCandidate.Something
if (!MatchNullConstant(conditional.IfTrue))
{
return result;
}
assignedCandidate = conditional.IfFalse;
}
else
{
// Pattern: memberCandidate NEQ null ? memberCandidate.Something : null
if (!MatchNullConstant(conditional.IfFalse))
{
return result;
}
assignedCandidate = conditional.IfTrue;
}
// Pattern can be one memberCandidate OP null or null OP memberCandidate.
Expression memberCandidate;
if (MatchNullConstant(equalityCheck.TestLeft))
{
memberCandidate = equalityCheck.TestRight;
}
else if (MatchNullConstant(equalityCheck.TestRight))
{
memberCandidate = equalityCheck.TestLeft;
}
else
{
return result;
}
Debug.Assert(assignedCandidate != null, "assignedCandidate != null");
Debug.Assert(memberCandidate != null, "memberCandidate != null");
// Verify that the member expression is a prefix path of the assigned expressions.
MemberAssignmentAnalysis assignedAnalysis = MemberAssignmentAnalysis.Analyze(entityInScope, assignedCandidate);
if (assignedAnalysis.MultiplePathsFound)
{
return result;
}
MemberAssignmentAnalysis memberAnalysis = MemberAssignmentAnalysis.Analyze(entityInScope, memberCandidate);
if (memberAnalysis.MultiplePathsFound)
{
return result;
}
Expression[] assignedExpressions = assignedAnalysis.GetExpressionsToTargetEntity();
Expression[] memberExpressions = memberAnalysis.GetExpressionsToTargetEntity();
if (memberExpressions.Length > assignedExpressions.Length)
{
return result;
}
// The access form we're interested in is [param].member0.member1...
for (int i = 0; i < memberExpressions.Length; i++)
{
Expression assigned = assignedExpressions[i];
Expression member = memberExpressions[i];
if (assigned == member)
{
continue;
}
if (assigned.NodeType != member.NodeType || assigned.NodeType != ExpressionType.MemberAccess)
{
return result;
}
if (((MemberExpression)assigned).Member != ((MemberExpression)member).Member)
{
return result;
}
}
result.AssignExpression = assignedCandidate;
result.Match = true;
result.TestToNullExpression = memberCandidate;
return result;
}
/// <summary>Checks whether the specified <paramref name="expression"/> is a null constant.</summary>
/// <param name="expression">Expression to check.</param>
/// <returns>true if <paramref name="expression"/> is a constant null value; false otherwise.</returns>
internal static bool MatchNullConstant(Expression expression)
{
Debug.Assert(expression != null, "expression != null");
ConstantExpression constant = expression as ConstantExpression;
if (constant != null && constant.Value == null)
{
return true;
}
return false;
}
internal static bool MatchBinaryExpression(Expression e)
{
return (e is BinaryExpression);
}
internal static bool MatchBinaryEquality(Expression e)
{
return (PatternRules.MatchBinaryExpression(e) && ((BinaryExpression)e).NodeType == ExpressionType.Equal);
}
internal static bool MatchStringAddition(Expression e)
{
if (e.NodeType == ExpressionType.Add)
{
BinaryExpression be = e as BinaryExpression;
return be != null &&
be.Left.Type == typeof(string) &&
be.Right.Type == typeof(string);
}
return false;
}
/// <summary>
/// Checks whether <paramref name="nex"/> is a "new DataServiceCollection of T".
/// </summary>
/// <param name="nex">The expression to match</param>
/// <returns>true if the expression matches the "new DataServiceCollection of T" or false otherwise.</returns>
internal static bool MatchNewDataServiceCollectionOfT(NewExpression nex)
{
return nex.Type.IsGenericType && WebUtil.IsDataServiceCollectionType(nex.Type.GetGenericTypeDefinition());
}
/// <summary>
/// Checks whether <paramref name="expression"/> is a check for
/// equality on two expressions.
/// </summary>
/// <param name="expression">Expression to match.</param>
/// <returns>
/// A structure describing whether the expression is a match,
/// whether it yields true on equality (ie, '==' as opposed to '!='),
/// and the compared expressions.
/// </returns>
/// <remarks>
/// This pattern recognizes the following:
/// - Calls to object.ReferenceEquals
/// - Equality checks (ExpressionNodeType.Equals and ExpressionNodeType.NotEquals)
/// - Negation (ExpressionNodeType.Not)
/// </remarks>
internal static MatchEqualityCheckResult MatchEquality(Expression expression)
{
Debug.Assert(expression != null, "expression != null");
// Before starting the pattern match, assume that we will not
// find one, and if we do, that it's a simple '==' check. The
// implementation needs to update these values as it traverses
// down the tree for nesting in expression such as !(a==b).
MatchEqualityCheckResult result = new MatchEqualityCheckResult();
result.Match = false;
result.EqualityYieldsTrue = true;
while (true)
{
if (MatchReferenceEquals(expression))
{
MethodCallExpression call = (MethodCallExpression)expression;
result.Match = true;
result.TestLeft = call.Arguments[0];
result.TestRight = call.Arguments[1];
break;
}
else if (MatchNot(expression))
{
result.EqualityYieldsTrue = !result.EqualityYieldsTrue;
expression = ((UnaryExpression)expression).Operand;
}
else
{
BinaryExpression test = expression as BinaryExpression;
if (test == null)
{
break;
}
if (test.NodeType == ExpressionType.NotEqual)
{
result.EqualityYieldsTrue = !result.EqualityYieldsTrue;
}
else if (test.NodeType != ExpressionType.Equal)
{
break;
}
result.TestLeft = test.Left;
result.TestRight = test.Right;
result.Match = true;
break;
}
}
return result;
}
/// <summary>
/// Checks whether the <paramref name="argument"/> expression is a
/// simple access (standalone or member-access'ed) on one of the
/// parameter <paramref name="expressions"/>.
/// </summary>
/// <param name="argument">Argument to match.</param>
/// <param name="expressions">Candidate parameters.</param>
/// <returns>
/// true if the argument is a parmater or a member from a
/// parameter; false otherwise.
/// </returns>
private static bool ExpressionIsSimpleAccess(Expression argument, ReadOnlyCollection<ParameterExpression> expressions)
{
Debug.Assert(argument != null, "argument != null");
Debug.Assert(expressions != null, "expressions != null");
Expression source = argument;
MemberExpression member;
do
{
member = source as MemberExpression;
if (member != null)
{
source = member.Expression;
}
}
while (member != null);
ParameterExpression parameter = source as ParameterExpression;
if (parameter == null)
{
return false;
}
return expressions.Contains(parameter);
}
/// <summary>
/// Checks whether the specified expression is a lambda with a parameterCount parameters
/// (possibly quoted).
/// </summary>
/// <param name="expression">Expression to match.</param>
/// <param name="parameterCount">Expected number of parametrs.</param>
/// <param name="lambda">If the expression matches, the lambda with the two parameters.</param>
/// <returns>true if the expression is a lambda with parameterCount parameters.</returns>
private static bool MatchNaryLambda(Expression expression, int parameterCount, out LambdaExpression lambda)
{
lambda = null;
LambdaExpression le = StripTo<LambdaExpression>(expression);
if (le != null && le.Parameters.Count == parameterCount)
{
lambda = le;
}
return lambda != null;
}
/// <summary>
/// Use this class to represent the results of a match on an expression
/// that does a null check before accessing a property.
/// </summary>
internal struct MatchNullCheckResult
{
/// <summary>Expression used to assign a value when the reference is not null.</summary>
internal Expression AssignExpression;
/// <summary>Whether the expression analyzed matches a null check pattern.</summary>
internal bool Match;
/// <summary>Expression being checked againt null.</summary>
internal Expression TestToNullExpression;
}
/// <summary>
/// Use this class to represent the results of a match on an expression
/// that checks for equality .
/// </summary>
internal struct MatchEqualityCheckResult
{
/// <summary>Whether a positive equality yields 'true' (ie, is this '==' as opposed to '!=').</summary>
internal bool EqualityYieldsTrue;
/// <summary>Whether the expression analyzed matches an equality check pattern.</summary>
internal bool Match;
/// <summary>The left-hand side of the check.</summary>
internal Expression TestLeft;
/// <summary>The right-hand side of the check.</summary>
internal Expression TestRight;
}
}
private static class ValidationRules
{
internal static void RequireCanNavigate(Expression e)
{
ResourceSetExpression resourceSet = e as ResourceSetExpression;
if (resourceSet != null && resourceSet.HasSequenceQueryOptions)
{
throw new NotSupportedException(Strings.ALinq_QueryOptionsOnlyAllowedOnLeafNodes);
}
ResourceExpression resource;
if (PatternRules.MatchResource(e, out resource) && resource.Projection != null)
{
throw new NotSupportedException(Strings.ALinq_ProjectionOnlyAllowedOnLeafNodes);
}
}
internal static void RequireCanProject(Expression e)
{
ResourceExpression re = (ResourceExpression)e;
if (!PatternRules.MatchResource(e, out re))
{
throw new NotSupportedException(Strings.ALinq_CanOnlyProjectTheLeaf);
}
if (re.Projection != null)
{
throw new NotSupportedException(Strings.ALinq_ProjectionCanOnlyHaveOneProjection);
}
if (re.ExpandPaths.Count > 0)
{
throw new NotSupportedException(Strings.ALinq_CannotProjectWithExplicitExpansion);
}
}
internal static void RequireCanExpand(Expression e)
{
ResourceExpression re = (ResourceExpression)e;
if (!PatternRules.MatchResource(e, out re))
{
throw new NotSupportedException(Strings.ALinq_CantExpand);
}
if (re.Projection != null)
{
throw new NotSupportedException(Strings.ALinq_CannotProjectWithExplicitExpansion);
}
}
internal static void RequireCanAddCount(Expression e)
{
ResourceExpression re = (ResourceExpression)e;
if (!PatternRules.MatchResource(e, out re))
{
throw new NotSupportedException(Strings.ALinq_CannotAddCountOption);
}
// do we already have a count option?
if (re.CountOption != CountOption.None)
{
throw new NotSupportedException(Strings.ALinq_CannotAddCountOptionConflict);
}
}
internal static void RequireCanAddCustomQueryOption(Expression e)
{
ResourceExpression re = (ResourceExpression)e;
if (!PatternRules.MatchResource(e, out re))
{
throw new NotSupportedException(Strings.ALinq_CantAddQueryOption);
}
}
internal static void RequireNonSingleton(Expression e)
{
ResourceExpression re = e as ResourceExpression;
if (re != null && re.IsSingleton)
{
throw new NotSupportedException(Strings.ALinq_QueryOptionsOnlyAllowedOnSingletons);
}
}
internal static void RequireLegalCustomQueryOption(Expression e, ResourceExpression target)
{
string name = ((string)(e as ConstantExpression).Value).Trim();
if (name[0] == UriHelper.DOLLARSIGN)
{
if (target.CustomQueryOptions.Any(c => (string)c.Key.Value == name))
{
// don't allow dups in Astoria $ namespace.
throw new NotSupportedException(Strings.ALinq_CantAddDuplicateQueryOption(name));
}
ResourceSetExpression rse = target as ResourceSetExpression;
if (rse != null)
{
switch (name.Substring(1))
{
case UriHelper.OPTIONFILTER:
if (rse.Filter != null)
{
throw new NotSupportedException(Strings.ALinq_CantAddAstoriaQueryOption(name));
}
break;
case UriHelper.OPTIONORDERBY:
if (rse.OrderBy != null)
throw new NotSupportedException(Strings.ALinq_CantAddAstoriaQueryOption(name));
break;
case UriHelper.OPTIONEXPAND:
break;
case UriHelper.OPTIONSKIP:
if (rse.Skip != null)
throw new NotSupportedException(Strings.ALinq_CantAddAstoriaQueryOption(name));
break;
case UriHelper.OPTIONTOP:
if (rse.Take != null)
throw new NotSupportedException(Strings.ALinq_CantAddAstoriaQueryOption(name));
break;
case UriHelper.OPTIONCOUNT:
// cannot add inlinecount if any counting already exists
if (rse.CountOption != CountOption.None)
throw new NotSupportedException(Strings.ALinq_CantAddAstoriaQueryOption(name));
break;
default:
throw new NotSupportedException(Strings.ALinq_CantAddQueryOptionStartingWithDollarSign(name));
}
}
}
}
}
// Devdiv Bug#489444: By default, C#/Vb compilers uses declaring type for property expression when
// we pass property name while creating the property expression. But its totally fine to use
// property info reflected from any subtype while creating property expressions.
// The problem is when one creates the property expression from a property info reflected from one
// of the subtype, then we don't recognize the key properties and instead of generating a key predicate, we generate
// a filter predicate. This limits a bunch of scenarios, since we can't navigate further once
// we generate a filter predicate.
// To fix this issue, we use a PropertyInfoEqualityComparer, which checks for the name and DeclaringType
// of the property and if they are the same, then it considers them equal.
/// <summary>
/// Equality and comparison implementation for PropertyInfo.
/// </summary>
private sealed class PropertyInfoEqualityComparer : IEqualityComparer<PropertyInfo>
{
/// <summary>
/// private constructor for the singleton pattern
/// </summary>
private PropertyInfoEqualityComparer() { }
/// <summary>
/// Static property which returns the single instance of the EqualityComparer
/// </summary>
internal static readonly PropertyInfoEqualityComparer Instance = new PropertyInfoEqualityComparer();
#region IEqualityComparer<TypeUsage> Members
/// <summary>
/// Checks whether the given property info's refers to the same property or not.
/// </summary>
/// <param name="left">first property info</param>
/// <param name="right">second property info</param>
/// <returns>true if they refer to the same property, otherwise returns false.</returns>
public bool Equals(PropertyInfo left, PropertyInfo right)
{
// Short circuit the comparison if we know the other reference is equivalent
if (object.ReferenceEquals(left, right)) { return true; }
// If either side is null, return false order (both can't be null because of
// the previous check)
if (null == left || null == right) { return false; }
// If the declaring type and the name of the property are the same,
// both the property infos refer to the same property.
return object.ReferenceEquals(left.DeclaringType, right.DeclaringType) && left.Name.Equals(right.Name);
}
/// <summary>
/// Computes the hashcode for the given property info
/// </summary>
/// <param name="obj">property info whose hash code needs to be computed.</param>
/// <returns>the hashcode for the given property info.</returns>
public int GetHashCode(PropertyInfo obj)
{
Debug.Assert(obj != null, "obj != null");
return (null != obj) ? obj.GetHashCode() : 0;
}
#endregion
}
/// <summary>
/// Use this visitor to detect whether an Expression is found in an
/// Expression tree.
/// </summary>
private sealed class ExpressionPresenceVisitor : DataServiceALinqExpressionVisitor
{
#region Private fields.
/// <summary>Target expression being sought.</summary>
private readonly Expression target;
/// <summary>Whether the target has been found.</summary>
private bool found;
#endregion Private fields.
/// <summary>
/// Initializes a new <see cref="ExpressionPresenceVisitor"/> that
/// searches for the given <paramref name="target"/>.
/// </summary>
/// <param name="target">Target expression to look for.</param>
private ExpressionPresenceVisitor(Expression target)
{
Debug.Assert(target != null, "target != null");
this.target = target;
}
/// <summary>
/// Checks whether the specified <paramref name="target"/> can
/// be found in the given <paramref name="tree"/>.
/// </summary>
/// <param name="target">Expression sought.</param>
/// <param name="tree">Expression tree to look into.</param>
/// <returns>true if target is found at least once; false otherwise.</returns>
internal static bool IsExpressionPresent(Expression target, Expression tree)
{
Debug.Assert(target != null, "target != null");
Debug.Assert(tree != null, "tree != null");
ExpressionPresenceVisitor visitor = new ExpressionPresenceVisitor(target);
visitor.Visit(tree);
return visitor.found;
}
/// <summary>Visits the specified expression.</summary>
/// <param name="exp">Expression to visit.</param>
/// <returns>The visited expression (<paramref name="exp"/>).</returns>
internal override Expression Visit(Expression exp)
{
Expression result;
// After finding the node, there is no need to visit any further.
if (this.found || object.ReferenceEquals(this.target, exp))
{
this.found = true;
result = exp;
}
else
{
result = base.Visit(exp);
}
return result;
}
}
}
}
|