Code:
/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / ndp / fx / src / DataWeb / Client / System / Data / Services / Client / ALinq / ResourceBinder.cs / 4 / ResourceBinder.cs
//---------------------------------------------------------------------- //// Copyright (c) Microsoft Corporation. All rights reserved. // //// Expression visitor that binds expression tree to resources // // // @owner [....] //--------------------------------------------------------------------- namespace System.Data.Services.Client { #region Namespaces. using System; using System.Collections.Generic; using System.Data.Services.Common; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; #endregion Namespaces. ////// This class provides a Bind method that analyzes an input internal class ResourceBinder : DataServiceExpressionVisitor { ///and returns a bound tree. /// /// Sets of private readonly HashSetthat require a key predicate that hasn't been satisfied yet. /// setsRequiringKeyPredicate = new HashSet (EqualityComparer .Default); /// Analyzes and binds the specified expression. /// Expression to bind. ///The expression with bound nodes. internal static Expression Bind(Expression e) { ResourceBinder rb = new ResourceBinder(); Expression boundExpression = rb.Visit(e); // Post Process bound tree // check to see if path segment ends in multi-valued property with no key predicate ResourceSetExpression rse = boundExpression as ResourceSetExpression; if (rse != null) { rse.IsLeafResource = true; } // If there are missing key predicates, only throw if translation was successful and the missing // key predicate is the only error. Otherwise, allow the expression that could not be translated // to propagate to the UriWriter which will throw to indicate that it is not supported. if ((boundExpression is ResourceExpression) && rb.setsRequiringKeyPredicate.Count > 0) { throw new NotSupportedException(Strings.ALinq_CantNavigateWithoutKeyPredicate); } return boundExpression; } private Expression AnalyzePredicate(MethodCallExpression mce) { // 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]+ Listconjuncts = new List (); AddConjuncts(le.Body, conjuncts); Dictionary > predicatesByTarget = new Dictionary >(); List referencedInputs = new List (); 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 targetPredicates = null; if (!predicatesByTarget.TryGetValue(boundTarget, out targetPredicates)) { targetPredicates = new List (); predicatesByTarget[boundTarget] = targetPredicates; } targetPredicates.Add(reboundPredicate); referencedInputs.Clear(); } conjuncts = null; List inputPredicates; if (predicatesByTarget.TryGetValue(input, out inputPredicates)) { predicatesByTarget.Remove(input); } else { inputPredicates = null; } foreach (KeyValuePair > predicates in predicatesByTarget) { ResourceSetExpression target = predicates.Key; List clauses = predicates.Value; Dictionary 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 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. inputPredicates.Add(BuildKeyPredicateFilter(input.CreateReference(), inputKeyValues)); } 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. inputPredicates.Add(BuildKeyPredicateFilter(input.CreateReference(), input.KeyPredicate)); 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 void SetKeyPredicate(ResourceSetExpression rse, Dictionary keyValues) { Debug.Assert(rse != null, "rse != null"); Debug.Assert(keyValues != null, "keyValues != null"); if (rse.KeyPredicate == null) { rse.KeyPredicate = new Dictionary (); } foreach(var g in keyValues) { if (rse.KeyPredicate.Keys.Contains(g.Key)) { //UNSUPPORTED: = AND = 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); } this.setsRequiringKeyPredicate.Remove(rse); } private static bool ExtractKeyPredicate(ResourceSetExpression target, List predicates, out Dictionary keyValues) { keyValues = null; List 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 (); } else if (keyValues.ContainsKey(property)) { // UNSUPPORTED: = AND = 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 (); } nonKeyPredicates.Add(predicate); } } Debug.Assert(keyValues != null || nonKeyPredicates != null, "No key predicates or non-key predicates found?"); if (keyValues != null && !PatternRules.GetKeyProperties(target.CreateReference().Type).SequenceEqual(keyValues.Keys)) { 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 keyValuesDictionary) { Debug.Assert(keyValuesDictionary.Count > 0, "At least one key property is required in a key predicate"); Expression retExpr = null; foreach (KeyValuePair 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; } private static void AddConjuncts(Expression e, List conjuncts) { if (PatternRules.MatchAnd(e)) { BinaryExpression be = (BinaryExpression)e; AddConjuncts(be.Left, conjuncts); AddConjuncts(be.Right, conjuncts); } else { conjuncts.Add(e); } } internal Expression AnalyzeProjection(MethodCallExpression mce) { Expression input = mce.Arguments[0]; LambdaExpression le; 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); } ResourceExpression navSource; Expression boundProjection; if (IsValidNavigationSource(input, out navSource) && TryBindToInput(navSource, le, out boundProjection) && PatternRules.MatchPropertyProjectionSingleton(navSource, boundProjection)) { return CreateNavigationPropertySingletonExpression(mce.Method.ReturnType, navSource, boundProjection); } return mce; } private bool IsValidNavigationSource(Expression input, out ResourceExpression sourceExpression) { ValidationRules.RequireCanNavigate(input); sourceExpression = input as ResourceExpression; if (sourceExpression != null && !sourceExpression.IsSingleton) { ResourceSetExpression rse = (ResourceSetExpression)sourceExpression; this.setsRequiringKeyPredicate.Add(rse); } return (sourceExpression != null); } internal Expression AnalyzeSelectMany(MethodCallExpression mce) { if (mce.Arguments.Count != 2 && mce.Arguments.Count != 3) { return mce; } ResourceExpression input; if (!IsValidNavigationSource(mce.Arguments[0], out input)) { return mce; } LambdaExpression le; if(!PatternRules.MatchSingleArgumentLambda(mce.Arguments[1], out le)) { return mce; } List referencedInputs = new List (); Expression navPropRef = InputBinder.Bind(le.Body, input, le.Parameters[0], referencedInputs); Type resourceType = TypeSystem.GetElementType(navPropRef.Type); // could be a Cast sequence operator here that can be optimized out. SequenceMethod sm; MethodCallExpression mce2 = navPropRef as MethodCallExpression; while (mce2 != null && ReflectionUtil.TryIdentifySequenceMethod(mce2.Method, out sm) && sm == SequenceMethod.Cast) { navPropRef = mce2.Arguments[0]; mce2 = navPropRef as MethodCallExpression; } ResourceSetExpression rse = null; if (PatternRules.MatchPropertyProjectionSet(input, navPropRef)) { rse = CreateResourceSetExpression(mce.Method.ReturnType, input, navPropRef, resourceType); } else { return mce; } if (mce.Arguments.Count == 3) { LambdaExpression selector = StripQuotes(mce.Arguments[2]) as LambdaExpression; // now examine selector // Check for transparent scope result - projects the input and the selector ResourceSetExpression.TransparentAccessors transparentScope; if (PatternRules.MatchTransparentScopeSelector(selector, out transparentScope)) { rse.TransparentScope = transparentScope; } else if (!PatternRules.MatchIdentityProjectionResultSelector(selector)) { return mce; } } return rse; } internal static Expression ApplyOrdering(MethodCallExpression mce, ResourceSetExpression input, Expression selector, bool descending, bool thenBy) { List selectors; if (!thenBy) { selectors = new List (); AddSequenceQueryOption(input, new OrderByQueryOptionExpression(mce.Type, selectors)); } else { Debug.Assert(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. /// Ensures that there's a limit on the cardinality of a query. ///for the method to limit First/Single(OrDefault). /// Maximum cardinality to allow. /// /// An expression that limits ///to no more than elements. /// This method is used by .First(OrDefault) and .Single(OrDefault) to limit cardinality. 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 rse = mce.Arguments[0] as ResourceExpression; if (rse != null) { return rse.Cast(mce.Method.ReturnType); } return mce; } private static Expression StripConvert(Expression e) { UnaryExpression ue = e as UnaryExpression; if (ue != null && ue.NodeType == ExpressionType.Convert && ue.Type.IsGenericType && ue.Type.GetGenericTypeDefinition() == typeof(DataServiceQuery<>)) { e = ue.Operand; ResourceExpression re = e as ResourceExpression; if (re != null) { e = re.Cast(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 = mce.Arguments[0] as ConstantExpression; re.ExpandPaths.Add(ce); 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); ValidationRules.RequireLegalCustomQueryOption(mce.Arguments[0], re); ConstantExpression name = mce.Arguments[0] as ConstantExpression; ConstantExpression value = mce.Arguments[1] as ConstantExpression; re.CustomQueryOptions.Add(name, value); return re; } private static ResourceSetExpression CreateResourceSetExpression(Type type, ResourceExpression source, Expression memberExpression, Type resourceType) { ResourceSetExpression newResource = new ResourceSetExpression(type, source, memberExpression, resourceType, source.ExpandPaths.ToList(), source.CustomQueryOptions.ToDictionary(kvp => kvp.Key, kvp => kvp.Value)); source.ExpandPaths.Clear(); source.CustomQueryOptions.Clear(); return newResource; } private static NavigationPropertySingletonExpression CreateNavigationPropertySingletonExpression(Type type, ResourceExpression source, Expression memberExpression) { NavigationPropertySingletonExpression newResource = new NavigationPropertySingletonExpression(type, source, memberExpression, source.ExpandPaths.ToList(), source.CustomQueryOptions.ToDictionary(kvp => kvp.Key, kvp => kvp.Value)); source.ExpandPaths.Clear(); source.CustomQueryOptions.Clear(); return newResource; } ////// Produces a new /// The result type -that is a clone of in all respects, /// other than its result type - which will be - and it's transparent scope, /// which will be null . This is a shallow clone operation - sequence query options, key predicate, etc are /// not cloned, but are reassigned to the new instance. Theresource expression should be /// discarded after being used with this method. /// - that the new resource set expression should have. /// The resource set expression from which the transparent scope is being removed /// A new resource set expression without an enclosing transparent scope and with the specified result type. 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.CustomQueryOptions); // 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; } private static Expression StripQuotes(Expression e) { return e.NodeType == ExpressionType.Quote ? ((UnaryExpression)e).Operand : e; } internal override Expression VisitResourceSetExpression(ResourceSetExpression rse) { if ((ResourceExpressionType)rse.NodeType == ResourceExpressionType.RootResourceSet) { Debug.Assert(!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, 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) { ListreferencedInputs = new List (); 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 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 constantMethodAnalyzer) { ResourceExpression input = (ResourceExpression)mce.Arguments[0]; ConstantExpression constantArg = mce.Arguments[1] as ConstantExpression; if (null == constantArg) { // UNSUPPORTED: A ConstantExpression is expected return mce; } return constantMethodAnalyzer(mce, input, constantArg); } 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")); } 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")); } 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 = e as BinaryExpression; 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 = e as MemberExpression; 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 = base.VisitMethodCall(mce); mce = e as MethodCallExpression; if (mce != null) { SequenceMethod sequenceMethod; if (ReflectionUtil.TryIdentifySequenceMethod(mce.Method, out sequenceMethod)) { switch (sequenceMethod) { case SequenceMethod.Where: return AnalyzePredicate(mce); case SequenceMethod.Select: return AnalyzeProjection(mce); case SequenceMethod.SelectMany: case SequenceMethod.SelectManyResultSelector: return AnalyzeSelectMany(mce); 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); 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 { throw Error.MethodNotSupported(mce); } } return mce; } return e; } private static class PatternRules { internal static bool MatchPropertyAccess(Expression e, out Expression instance, out List propertyPath) { instance = null; propertyPath = null; MemberExpression me = e as MemberExpression; while (me != null) { PropertyInfo pi; if (MatchNonPrivateReadableProperty(me, out pi)) { if (propertyPath == null) { propertyPath = new List (); } propertyPath.Insert(0, pi.Name); e = me.Expression; me = e as MemberExpression; } 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); } internal static bool MatchNonPrivateReadableProperty(MemberExpression me, out PropertyInfo propInfo) { Debug.Assert(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; } /// /// Checks whether the specified /// Expression to check. /// If this is a key access, the property for the key. ///is a member access to a key. /// true if 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)) { property = pi; return true; } return false; } ///is a member access to a key; false otherwise. Gets the key properties from the specified /// Type to get properties from. ///. A list of properties that are key for the type; possibly an empty list. internal static ListGetKeyProperties(Type type) { Debug.Assert(type != null, "type != null"); ClientType clientType = ClientType.Create(type, false /* expectModelType */); var result = new List (); 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; } internal static bool MatchResourceSet(Expression e) { return (e is ResourceSetExpression); } internal static bool MatchResource(Expression e) { return (e is ResourceExpression); } internal static bool MatchSingleArgumentLambda(Expression e, out LambdaExpression lambda) { lambda = null; LambdaExpression le = StripQuotes(e) as LambdaExpression; if (le != null && le.Parameters.Count == 1) { lambda = le; } return (lambda != null); } internal static bool MatchIdentitySelector(Expression e) { LambdaExpression le = (LambdaExpression)e; return (le.Body == le.Parameters[0]); } internal static bool MatchTransparentIdentitySelector(Expression input, LambdaExpression selector) { if (selector.Parameters.Count != 1) { return false; } return MatchTransparentScopeAccess(input, selector.Body, selector.Parameters[0]); } private static bool MatchTransparentScopeAccess(Expression input, Expression potentialRef, ParameterExpression expectedTarget) { ResourceSetExpression rse = input as ResourceSetExpression; if (rse == null || rse.TransparentScope == null) { return false; } Expression paramRef; List refPath; return (MatchPropertyAccess(potentialRef, out paramRef, out refPath) && paramRef == expectedTarget && refPath != null && refPath.Count == 1 && string.Equals(refPath[0], rse.TransparentScope.Accessor, StringComparison.Ordinal)); } internal static bool MatchIdentityProjectionResultSelector(Expression e) { LambdaExpression le = (LambdaExpression)e; return (le.Body == le.Parameters[1]); } internal static bool MatchTransparentScopeSelector(Expression e, out ResourceSetExpression.TransparentAccessors transparentScope) { transparentScope = null; LambdaExpression le = (LambdaExpression)e; if (le.Body.NodeType == ExpressionType.New) { NewExpression ne = (NewExpression)le.Body; if (ne.Constructor.GetParameters().Length == 2 && ne.Arguments[0] == le.Parameters[0] && ne.Arguments[1] == le.Parameters[1]) { PropertyInfo[] publicProps = le.Body.Type.GetProperties(BindingFlags.Instance | BindingFlags.Public); if (publicProps.Length == 2) { if (le.Body.Type.BaseType.Equals(typeof(object)) && publicProps[0].Name.Equals(le.Parameters[0].Name, StringComparison.Ordinal) && publicProps[1].Name.Equals(le.Parameters[1].Name, StringComparison.Ordinal)) { transparentScope = new ResourceSetExpression.TransparentAccessors( publicProps[1].Name, publicProps[0].Name ); return true; } } } } return false; } internal static bool MatchPropertyProjectionSet(ResourceExpression input, Expression potentialPropertyRef) { return MatchNavigationPropertyProjection(input, potentialPropertyRef, true); } internal static bool MatchPropertyProjectionSingleton(ResourceExpression input, Expression potentialPropertyRef) { return MatchNavigationPropertyProjection(input, potentialPropertyRef, false); } private static bool MatchNavigationPropertyProjection(ResourceExpression input, Expression potentialPropertyRef, bool requireSet) { Expression foundInstance; List propertyNames; return (PatternRules.MatchNonSingletonProperty(potentialPropertyRef) == requireSet && MatchPropertyAccess(potentialPropertyRef, out foundInstance, out propertyNames) && foundInstance == input.CreateReference()); } 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[]); } 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; } } private static class ValidationRules { internal static void RequireCanNavigate(Expression e) { if (PatternRules.MatchResourceSet(e) && ((ResourceSetExpression)e).HasSequenceQueryOptions) { throw new NotSupportedException(Strings.ALinq_QueryOptionsOnlyAllowedOnLeafNodes); } } internal static void RequireCanExpand(Expression e) { if (!PatternRules.MatchResource(e)) { throw new NotSupportedException(Strings.ALinq_CantExpand); } } internal static void RequireCanAddCustomQueryOption(Expression e) { if (!PatternRules.MatchResource(e)) { 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: if (rse.ExpandPaths.Count > 0) throw new NotSupportedException(Strings.ALinq_CantAddAstoriaQueryOption(name)); 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; default: throw new NotSupportedException(Strings.ALinq_CantAddQueryOptionStartingWithDollarSign(name)); } } } } } } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. //---------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // //// Expression visitor that binds expression tree to resources // // // @owner [....] //--------------------------------------------------------------------- namespace System.Data.Services.Client { #region Namespaces. using System; using System.Collections.Generic; using System.Data.Services.Common; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; #endregion Namespaces. ////// This class provides a Bind method that analyzes an input internal class ResourceBinder : DataServiceExpressionVisitor { ///and returns a bound tree. /// /// Sets of private readonly HashSetthat require a key predicate that hasn't been satisfied yet. /// setsRequiringKeyPredicate = new HashSet (EqualityComparer .Default); /// Analyzes and binds the specified expression. /// Expression to bind. ///The expression with bound nodes. internal static Expression Bind(Expression e) { ResourceBinder rb = new ResourceBinder(); Expression boundExpression = rb.Visit(e); // Post Process bound tree // check to see if path segment ends in multi-valued property with no key predicate ResourceSetExpression rse = boundExpression as ResourceSetExpression; if (rse != null) { rse.IsLeafResource = true; } // If there are missing key predicates, only throw if translation was successful and the missing // key predicate is the only error. Otherwise, allow the expression that could not be translated // to propagate to the UriWriter which will throw to indicate that it is not supported. if ((boundExpression is ResourceExpression) && rb.setsRequiringKeyPredicate.Count > 0) { throw new NotSupportedException(Strings.ALinq_CantNavigateWithoutKeyPredicate); } return boundExpression; } private Expression AnalyzePredicate(MethodCallExpression mce) { // 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]+ Listconjuncts = new List (); AddConjuncts(le.Body, conjuncts); Dictionary > predicatesByTarget = new Dictionary >(); List referencedInputs = new List (); 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 targetPredicates = null; if (!predicatesByTarget.TryGetValue(boundTarget, out targetPredicates)) { targetPredicates = new List (); predicatesByTarget[boundTarget] = targetPredicates; } targetPredicates.Add(reboundPredicate); referencedInputs.Clear(); } conjuncts = null; List inputPredicates; if (predicatesByTarget.TryGetValue(input, out inputPredicates)) { predicatesByTarget.Remove(input); } else { inputPredicates = null; } foreach (KeyValuePair > predicates in predicatesByTarget) { ResourceSetExpression target = predicates.Key; List clauses = predicates.Value; Dictionary 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 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. inputPredicates.Add(BuildKeyPredicateFilter(input.CreateReference(), inputKeyValues)); } 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. inputPredicates.Add(BuildKeyPredicateFilter(input.CreateReference(), input.KeyPredicate)); 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 void SetKeyPredicate(ResourceSetExpression rse, Dictionary keyValues) { Debug.Assert(rse != null, "rse != null"); Debug.Assert(keyValues != null, "keyValues != null"); if (rse.KeyPredicate == null) { rse.KeyPredicate = new Dictionary (); } foreach(var g in keyValues) { if (rse.KeyPredicate.Keys.Contains(g.Key)) { //UNSUPPORTED: = AND = 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); } this.setsRequiringKeyPredicate.Remove(rse); } private static bool ExtractKeyPredicate(ResourceSetExpression target, List predicates, out Dictionary keyValues) { keyValues = null; List 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 (); } else if (keyValues.ContainsKey(property)) { // UNSUPPORTED: = AND = 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 (); } nonKeyPredicates.Add(predicate); } } Debug.Assert(keyValues != null || nonKeyPredicates != null, "No key predicates or non-key predicates found?"); if (keyValues != null && !PatternRules.GetKeyProperties(target.CreateReference().Type).SequenceEqual(keyValues.Keys)) { 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 keyValuesDictionary) { Debug.Assert(keyValuesDictionary.Count > 0, "At least one key property is required in a key predicate"); Expression retExpr = null; foreach (KeyValuePair 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; } private static void AddConjuncts(Expression e, List conjuncts) { if (PatternRules.MatchAnd(e)) { BinaryExpression be = (BinaryExpression)e; AddConjuncts(be.Left, conjuncts); AddConjuncts(be.Right, conjuncts); } else { conjuncts.Add(e); } } internal Expression AnalyzeProjection(MethodCallExpression mce) { Expression input = mce.Arguments[0]; LambdaExpression le; 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); } ResourceExpression navSource; Expression boundProjection; if (IsValidNavigationSource(input, out navSource) && TryBindToInput(navSource, le, out boundProjection) && PatternRules.MatchPropertyProjectionSingleton(navSource, boundProjection)) { return CreateNavigationPropertySingletonExpression(mce.Method.ReturnType, navSource, boundProjection); } return mce; } private bool IsValidNavigationSource(Expression input, out ResourceExpression sourceExpression) { ValidationRules.RequireCanNavigate(input); sourceExpression = input as ResourceExpression; if (sourceExpression != null && !sourceExpression.IsSingleton) { ResourceSetExpression rse = (ResourceSetExpression)sourceExpression; this.setsRequiringKeyPredicate.Add(rse); } return (sourceExpression != null); } internal Expression AnalyzeSelectMany(MethodCallExpression mce) { if (mce.Arguments.Count != 2 && mce.Arguments.Count != 3) { return mce; } ResourceExpression input; if (!IsValidNavigationSource(mce.Arguments[0], out input)) { return mce; } LambdaExpression le; if(!PatternRules.MatchSingleArgumentLambda(mce.Arguments[1], out le)) { return mce; } List referencedInputs = new List (); Expression navPropRef = InputBinder.Bind(le.Body, input, le.Parameters[0], referencedInputs); Type resourceType = TypeSystem.GetElementType(navPropRef.Type); // could be a Cast sequence operator here that can be optimized out. SequenceMethod sm; MethodCallExpression mce2 = navPropRef as MethodCallExpression; while (mce2 != null && ReflectionUtil.TryIdentifySequenceMethod(mce2.Method, out sm) && sm == SequenceMethod.Cast) { navPropRef = mce2.Arguments[0]; mce2 = navPropRef as MethodCallExpression; } ResourceSetExpression rse = null; if (PatternRules.MatchPropertyProjectionSet(input, navPropRef)) { rse = CreateResourceSetExpression(mce.Method.ReturnType, input, navPropRef, resourceType); } else { return mce; } if (mce.Arguments.Count == 3) { LambdaExpression selector = StripQuotes(mce.Arguments[2]) as LambdaExpression; // now examine selector // Check for transparent scope result - projects the input and the selector ResourceSetExpression.TransparentAccessors transparentScope; if (PatternRules.MatchTransparentScopeSelector(selector, out transparentScope)) { rse.TransparentScope = transparentScope; } else if (!PatternRules.MatchIdentityProjectionResultSelector(selector)) { return mce; } } return rse; } internal static Expression ApplyOrdering(MethodCallExpression mce, ResourceSetExpression input, Expression selector, bool descending, bool thenBy) { List selectors; if (!thenBy) { selectors = new List (); AddSequenceQueryOption(input, new OrderByQueryOptionExpression(mce.Type, selectors)); } else { Debug.Assert(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. /// Ensures that there's a limit on the cardinality of a query. ///for the method to limit First/Single(OrDefault). /// Maximum cardinality to allow. /// /// An expression that limits ///to no more than elements. /// This method is used by .First(OrDefault) and .Single(OrDefault) to limit cardinality. 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 rse = mce.Arguments[0] as ResourceExpression; if (rse != null) { return rse.Cast(mce.Method.ReturnType); } return mce; } private static Expression StripConvert(Expression e) { UnaryExpression ue = e as UnaryExpression; if (ue != null && ue.NodeType == ExpressionType.Convert && ue.Type.IsGenericType && ue.Type.GetGenericTypeDefinition() == typeof(DataServiceQuery<>)) { e = ue.Operand; ResourceExpression re = e as ResourceExpression; if (re != null) { e = re.Cast(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 = mce.Arguments[0] as ConstantExpression; re.ExpandPaths.Add(ce); 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); ValidationRules.RequireLegalCustomQueryOption(mce.Arguments[0], re); ConstantExpression name = mce.Arguments[0] as ConstantExpression; ConstantExpression value = mce.Arguments[1] as ConstantExpression; re.CustomQueryOptions.Add(name, value); return re; } private static ResourceSetExpression CreateResourceSetExpression(Type type, ResourceExpression source, Expression memberExpression, Type resourceType) { ResourceSetExpression newResource = new ResourceSetExpression(type, source, memberExpression, resourceType, source.ExpandPaths.ToList(), source.CustomQueryOptions.ToDictionary(kvp => kvp.Key, kvp => kvp.Value)); source.ExpandPaths.Clear(); source.CustomQueryOptions.Clear(); return newResource; } private static NavigationPropertySingletonExpression CreateNavigationPropertySingletonExpression(Type type, ResourceExpression source, Expression memberExpression) { NavigationPropertySingletonExpression newResource = new NavigationPropertySingletonExpression(type, source, memberExpression, source.ExpandPaths.ToList(), source.CustomQueryOptions.ToDictionary(kvp => kvp.Key, kvp => kvp.Value)); source.ExpandPaths.Clear(); source.CustomQueryOptions.Clear(); return newResource; } ////// Produces a new /// The result type -that is a clone of in all respects, /// other than its result type - which will be - and it's transparent scope, /// which will be null . This is a shallow clone operation - sequence query options, key predicate, etc are /// not cloned, but are reassigned to the new instance. Theresource expression should be /// discarded after being used with this method. /// - that the new resource set expression should have. /// The resource set expression from which the transparent scope is being removed /// A new resource set expression without an enclosing transparent scope and with the specified result type. 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.CustomQueryOptions); // 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; } private static Expression StripQuotes(Expression e) { return e.NodeType == ExpressionType.Quote ? ((UnaryExpression)e).Operand : e; } internal override Expression VisitResourceSetExpression(ResourceSetExpression rse) { if ((ResourceExpressionType)rse.NodeType == ResourceExpressionType.RootResourceSet) { Debug.Assert(!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, 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) { ListreferencedInputs = new List (); 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 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 constantMethodAnalyzer) { ResourceExpression input = (ResourceExpression)mce.Arguments[0]; ConstantExpression constantArg = mce.Arguments[1] as ConstantExpression; if (null == constantArg) { // UNSUPPORTED: A ConstantExpression is expected return mce; } return constantMethodAnalyzer(mce, input, constantArg); } 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")); } 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")); } 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 = e as BinaryExpression; 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 = e as MemberExpression; 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 = base.VisitMethodCall(mce); mce = e as MethodCallExpression; if (mce != null) { SequenceMethod sequenceMethod; if (ReflectionUtil.TryIdentifySequenceMethod(mce.Method, out sequenceMethod)) { switch (sequenceMethod) { case SequenceMethod.Where: return AnalyzePredicate(mce); case SequenceMethod.Select: return AnalyzeProjection(mce); case SequenceMethod.SelectMany: case SequenceMethod.SelectManyResultSelector: return AnalyzeSelectMany(mce); 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); 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 { throw Error.MethodNotSupported(mce); } } return mce; } return e; } private static class PatternRules { internal static bool MatchPropertyAccess(Expression e, out Expression instance, out List propertyPath) { instance = null; propertyPath = null; MemberExpression me = e as MemberExpression; while (me != null) { PropertyInfo pi; if (MatchNonPrivateReadableProperty(me, out pi)) { if (propertyPath == null) { propertyPath = new List (); } propertyPath.Insert(0, pi.Name); e = me.Expression; me = e as MemberExpression; } 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); } internal static bool MatchNonPrivateReadableProperty(MemberExpression me, out PropertyInfo propInfo) { Debug.Assert(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; } /// /// Checks whether the specified /// Expression to check. /// If this is a key access, the property for the key. ///is a member access to a key. /// true if 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)) { property = pi; return true; } return false; } ///is a member access to a key; false otherwise. Gets the key properties from the specified /// Type to get properties from. ///. A list of properties that are key for the type; possibly an empty list. internal static ListGetKeyProperties(Type type) { Debug.Assert(type != null, "type != null"); ClientType clientType = ClientType.Create(type, false /* expectModelType */); var result = new List (); 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; } internal static bool MatchResourceSet(Expression e) { return (e is ResourceSetExpression); } internal static bool MatchResource(Expression e) { return (e is ResourceExpression); } internal static bool MatchSingleArgumentLambda(Expression e, out LambdaExpression lambda) { lambda = null; LambdaExpression le = StripQuotes(e) as LambdaExpression; if (le != null && le.Parameters.Count == 1) { lambda = le; } return (lambda != null); } internal static bool MatchIdentitySelector(Expression e) { LambdaExpression le = (LambdaExpression)e; return (le.Body == le.Parameters[0]); } internal static bool MatchTransparentIdentitySelector(Expression input, LambdaExpression selector) { if (selector.Parameters.Count != 1) { return false; } return MatchTransparentScopeAccess(input, selector.Body, selector.Parameters[0]); } private static bool MatchTransparentScopeAccess(Expression input, Expression potentialRef, ParameterExpression expectedTarget) { ResourceSetExpression rse = input as ResourceSetExpression; if (rse == null || rse.TransparentScope == null) { return false; } Expression paramRef; List refPath; return (MatchPropertyAccess(potentialRef, out paramRef, out refPath) && paramRef == expectedTarget && refPath != null && refPath.Count == 1 && string.Equals(refPath[0], rse.TransparentScope.Accessor, StringComparison.Ordinal)); } internal static bool MatchIdentityProjectionResultSelector(Expression e) { LambdaExpression le = (LambdaExpression)e; return (le.Body == le.Parameters[1]); } internal static bool MatchTransparentScopeSelector(Expression e, out ResourceSetExpression.TransparentAccessors transparentScope) { transparentScope = null; LambdaExpression le = (LambdaExpression)e; if (le.Body.NodeType == ExpressionType.New) { NewExpression ne = (NewExpression)le.Body; if (ne.Constructor.GetParameters().Length == 2 && ne.Arguments[0] == le.Parameters[0] && ne.Arguments[1] == le.Parameters[1]) { PropertyInfo[] publicProps = le.Body.Type.GetProperties(BindingFlags.Instance | BindingFlags.Public); if (publicProps.Length == 2) { if (le.Body.Type.BaseType.Equals(typeof(object)) && publicProps[0].Name.Equals(le.Parameters[0].Name, StringComparison.Ordinal) && publicProps[1].Name.Equals(le.Parameters[1].Name, StringComparison.Ordinal)) { transparentScope = new ResourceSetExpression.TransparentAccessors( publicProps[1].Name, publicProps[0].Name ); return true; } } } } return false; } internal static bool MatchPropertyProjectionSet(ResourceExpression input, Expression potentialPropertyRef) { return MatchNavigationPropertyProjection(input, potentialPropertyRef, true); } internal static bool MatchPropertyProjectionSingleton(ResourceExpression input, Expression potentialPropertyRef) { return MatchNavigationPropertyProjection(input, potentialPropertyRef, false); } private static bool MatchNavigationPropertyProjection(ResourceExpression input, Expression potentialPropertyRef, bool requireSet) { Expression foundInstance; List propertyNames; return (PatternRules.MatchNonSingletonProperty(potentialPropertyRef) == requireSet && MatchPropertyAccess(potentialPropertyRef, out foundInstance, out propertyNames) && foundInstance == input.CreateReference()); } 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[]); } 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; } } private static class ValidationRules { internal static void RequireCanNavigate(Expression e) { if (PatternRules.MatchResourceSet(e) && ((ResourceSetExpression)e).HasSequenceQueryOptions) { throw new NotSupportedException(Strings.ALinq_QueryOptionsOnlyAllowedOnLeafNodes); } } internal static void RequireCanExpand(Expression e) { if (!PatternRules.MatchResource(e)) { throw new NotSupportedException(Strings.ALinq_CantExpand); } } internal static void RequireCanAddCustomQueryOption(Expression e) { if (!PatternRules.MatchResource(e)) { 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: if (rse.ExpandPaths.Count > 0) throw new NotSupportedException(Strings.ALinq_CantAddAstoriaQueryOption(name)); 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; default: throw new NotSupportedException(Strings.ALinq_CantAddQueryOptionStartingWithDollarSign(name)); } } } } } } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007.
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- SubMenuStyle.cs
- EncodingNLS.cs
- FixedPosition.cs
- DataGridViewCellValueEventArgs.cs
- DebugView.cs
- XmlSchemaAll.cs
- OdbcParameter.cs
- BookmarkManager.cs
- LocationUpdates.cs
- XmlRootAttribute.cs
- Model3D.cs
- CodeCatchClauseCollection.cs
- MsmqBindingElementBase.cs
- SettingsPropertyCollection.cs
- BoundField.cs
- RevocationPoint.cs
- CryptoKeySecurity.cs
- Int32CollectionConverter.cs
- Publisher.cs
- HotSpot.cs
- ToolStripOverflowButton.cs
- safesecurityhelperavalon.cs
- SafeFindHandle.cs
- RouteItem.cs
- StringFreezingAttribute.cs
- WebPageTraceListener.cs
- CfgParser.cs
- DataControlImageButton.cs
- DataGridViewAdvancedBorderStyle.cs
- HybridWebProxyFinder.cs
- SubMenuStyleCollection.cs
- Rect3DValueSerializer.cs
- CacheSection.cs
- AlignmentXValidation.cs
- ScriptMethodAttribute.cs
- FontUnitConverter.cs
- ProtectedConfiguration.cs
- XPathEmptyIterator.cs
- FlowLayoutSettings.cs
- BuildResult.cs
- ADMembershipUser.cs
- SqlClientFactory.cs
- InputBuffer.cs
- SafeRightsManagementEnvironmentHandle.cs
- BamlRecordReader.cs
- TrackBarRenderer.cs
- _SslStream.cs
- WebEventCodes.cs
- Currency.cs
- SafePEFileHandle.cs
- DefaultEventAttribute.cs
- XmlAttributeOverrides.cs
- MembershipValidatePasswordEventArgs.cs
- EditorPart.cs
- CallbackHandler.cs
- ReachPrintTicketSerializerAsync.cs
- Literal.cs
- TabPage.cs
- InvalidWMPVersionException.cs
- RNGCryptoServiceProvider.cs
- MetabaseSettings.cs
- LoginCancelEventArgs.cs
- ExpressionBuilder.cs
- TypedTableGenerator.cs
- ConfigUtil.cs
- ISSmlParser.cs
- WSHttpBindingBase.cs
- DataGridItemCollection.cs
- ShaderEffect.cs
- EpmSourcePathSegment.cs
- EnvironmentPermission.cs
- FormParameter.cs
- ToolStripContentPanelRenderEventArgs.cs
- PropertyInformationCollection.cs
- CheckableControlBaseAdapter.cs
- _OverlappedAsyncResult.cs
- CodeEntryPointMethod.cs
- FixedPageProcessor.cs
- Operand.cs
- PerformanceCounterCategory.cs
- ComponentDispatcher.cs
- PreviewPageInfo.cs
- CaseInsensitiveHashCodeProvider.cs
- VideoDrawing.cs
- SortDescription.cs
- PermissionListSet.cs
- _ChunkParse.cs
- AppDomainProtocolHandler.cs
- Stylesheet.cs
- ChannelSinkStacks.cs
- SecurityHelper.cs
- AssertSection.cs
- ManagementOptions.cs
- StringAnimationBase.cs
- Variant.cs
- NamespaceCollection.cs
- Pen.cs
- RemoteEndpointMessageProperty.cs
- GlyphRunDrawing.cs
- DataGridViewCheckBoxCell.cs