/// <summary> /// Visit an <see cref="InputReferenceExpression"/>, producing a new InputReferenceExpression /// based on the visited form of the <see cref="QueryableResourceExpression"/> that is referenced by /// the InputReferenceExpression argument, <paramref name="ire"/>. /// </summary> /// <param name="ire">InputReferenceExpression expression to visit</param> /// <returns>Visited InputReferenceExpression expression</returns> internal virtual Expression VisitInputReferenceExpression(InputReferenceExpression ire) { Debug.Assert(ire != null, "ire != null -- otherwise caller never should have visited here"); ResourceExpression re = (ResourceExpression)this.Visit(ire.Target); return(re.CreateReference()); }
/// <summary> /// Analyzes a lambda expression to check whether it can be satisfied with /// $select and client-side materialization. /// </summary> /// <param name="le">Lambda expression.</param> /// <param name="re">Resource expression in scope.</param> /// <param name="matchMembers">Whether member accesses are matched as top-level projections.</param> /// <param name="context">Context of expression to analyze.</param> /// <returns>true if the lambda is a client-side projection; false otherwise.</returns> internal static bool Analyze(LambdaExpression le, ResourceExpression re, bool matchMembers, DataServiceContext context) { Debug.Assert(le != null, "le != null"); if (le.Body.NodeType == ExpressionType.Constant) { if (ClientTypeUtil.TypeOrElementTypeIsEntity(le.Body.Type)) { throw new NotSupportedException(Strings.ALinq_CannotCreateConstantEntity); } re.Projection = new ProjectionQueryOptionExpression(le.Body.Type, le, new List<string>()); return true; } if (le.Body.NodeType == ExpressionType.MemberInit || le.Body.NodeType == ExpressionType.New) { AnalyzeResourceExpression(le, re, context); return true; } if (matchMembers) { // Members can be projected standalone or type-casted. Expression withoutConverts = SkipConverts(le.Body); if (withoutConverts.NodeType == ExpressionType.MemberAccess) { AnalyzeResourceExpression(le, re, context); return true; } } return false; }
/// <summary> /// Retargets this input reference to point to the resource set specified by <paramref name="newTarget"/>. /// </summary> /// <param name="newTarget">The <see cref="QueryableResourceExpression"/> that this input reference should use as its target</param> internal void OverrideTarget(QueryableResourceExpression newTarget) { Debug.Assert(newTarget != null, "Resource set cannot be null"); Debug.Assert(newTarget.ResourceType.Equals(this.Type), "Cannot reference a resource set with a different resource type"); this.target = newTarget; }
/// <summary> /// Analyzes a lambda expression to check whether it can be satisfied with /// $select and client-side materialization. /// </summary> /// <param name="le">Lambda expression.</param> /// <param name="re">Resource expression in scope.</param> /// <param name="matchMembers">Whether member accesses are matched as top-level projections.</param> /// <param name="context">Context of expression to analyze.</param> /// <returns>true if the lambda is a client-side projection; false otherwise.</returns> internal static bool Analyze(LambdaExpression le, ResourceExpression re, bool matchMembers, DataServiceContext context) { Debug.Assert(le != null, "le != null"); if (le.Body.NodeType == ExpressionType.Constant) { if (ClientTypeUtil.TypeOrElementTypeIsEntity(le.Body.Type)) { throw new NotSupportedException(Strings.ALinq_CannotCreateConstantEntity); } re.Projection = new ProjectionQueryOptionExpression(le.Body.Type, le, new List <string>()); return(true); } if (le.Body.NodeType == ExpressionType.MemberInit || le.Body.NodeType == ExpressionType.New) { AnalyzeResourceExpression(le, re, context); return(true); } if (matchMembers) { // Members can be projected standalone or type-casted. Expression withoutConverts = SkipConverts(le.Body); if (withoutConverts.NodeType == ExpressionType.MemberAccess) { AnalyzeResourceExpression(le, re, context); return(true); } } return(false); }
/// <summary>Builds the Uri for the expression passed in.</summary> /// <param name="e">The expression to translate into a Uri</param> /// <returns>Query components</returns> internal QueryComponents Translate(Expression e) { Uri uri; Version version; bool addTrailingParens = false; Dictionary <Expression, Expression> normalizerRewrites = null; // short cut analysis if just a resource set or singleton resource. // note - to be backwards compatible with V1, will only append trailing () for queries // that include more then just a resource set. if (!(e is QueryableResourceExpression)) { normalizerRewrites = new Dictionary <Expression, Expression>(ReferenceEqualityComparer <Expression> .Instance); e = Evaluator.PartialEval(e); e = ExpressionNormalizer.Normalize(e, normalizerRewrites); e = ResourceBinder.Bind(e, this.Context); addTrailingParens = true; } UriWriter.Translate(this.Context, addTrailingParens, e, out uri, out version); ResourceExpression re = e as ResourceExpression; Type lastSegmentType = re.Projection == null ? re.ResourceType : re.Projection.Selector.Parameters[0].Type; LambdaExpression selector = re.Projection == null ? null : re.Projection.Selector; return(new QueryComponents(uri, version, lastSegmentType, selector, normalizerRewrites)); }
/// <summary> /// Visit Query options for Resource /// </summary> /// <param name="re">Resource Expression with query options</param> internal void VisitQueryOptions(ResourceExpression re) { if (re.HasQueryOptions) { this.uriBuilder.Append(UriHelper.QUESTIONMARK); QueryableResourceExpression rse = re as QueryableResourceExpression; if (rse != null) { IEnumerator options = rse.SequenceQueryOptions.GetEnumerator(); while (options.MoveNext()) { Expression e = ((Expression)options.Current); ResourceExpressionType et = (ResourceExpressionType)e.NodeType; switch (et) { case ResourceExpressionType.SkipQueryOption: this.VisitQueryOptionExpression((SkipQueryOptionExpression)e); break; case ResourceExpressionType.TakeQueryOption: this.VisitQueryOptionExpression((TakeQueryOptionExpression)e); break; case ResourceExpressionType.OrderByQueryOption: this.VisitQueryOptionExpression((OrderByQueryOptionExpression)e); break; case ResourceExpressionType.FilterQueryOption: this.VisitQueryOptionExpression((FilterQueryOptionExpression)e); break; default: Debug.Assert(false, "Unexpected expression type " + (int)et); break; } } } if (re.ExpandPaths.Count > 0) { this.VisitExpandOptions(re.ExpandPaths); } if (re.Projection != null && re.Projection.Paths.Count > 0) { this.VisitProjectionPaths(re.Projection.Paths); } if (re.CountOption == CountOption.CountQuery) { this.VisitCountQueryOptions(); } if (re.CustomQueryOptions.Count > 0) { this.VisitCustomQueryOptions(re.CustomQueryOptions); } this.AppendCachedQueryOptionsToUriBuilder(); } }
/// <summary> /// Analyzes the specified <paramref name="lambda"/> for selection and updates /// <paramref name="resource"/>. /// </summary> /// <param name="lambda">Lambda expression to analyze.</param> /// <param name="resource">Resource expression to update.</param> /// <param name="context">Context of expression to analyze.</param> private static void AnalyzeResourceExpression(LambdaExpression lambda, ResourceExpression resource, DataServiceContext context) { SelectExpandPathBuilder pb = new SelectExpandPathBuilder(); ProjectionAnalyzer.Analyze(lambda, pb, context); resource.Projection = new ProjectionQueryOptionExpression(lambda.Body.Type, lambda, pb.ProjectionPaths.ToList()); resource.ExpandPaths = pb.ExpandPaths.Union(resource.ExpandPaths, StringComparer.Ordinal).ToList(); resource.RaiseUriVersion(pb.UriVersion); }
/// <summary> /// Replaces Lambda parameter references or transparent scope property accesses over those Lambda /// parameter references with <see cref="InputReferenceExpression"/>s to the appropriate corresponding /// <see cref="QueryableResourceExpression"/>s, based on the 'input' QueryableResourceExpression to which the /// Lambda is logically applied and any enclosing transparent scope applied to that input resource. /// </summary> /// <param name="e">The expression to rebind</param> /// <param name="currentInput"> /// The 'current input' resource - either the root resource or the /// rightmost resource in the navigation chain.</param> /// <param name="inputParameter">The Lambda parameter that represents a reference to the 'input'</param> /// <param name="referencedInputs">A list that will be populated with the resources that were referenced by the rebound expression</param> /// <returns> /// The rebound version of <paramref name="e"/> where MemberExpression/ParameterExpressions that /// represent resource references have been replaced with appropriate InputReferenceExpressions. /// </returns> internal static Expression Bind(Expression e, ResourceExpression currentInput, ParameterExpression inputParameter, List<ResourceExpression> referencedInputs) { Debug.Assert(e != null, "Expression cannot be null"); Debug.Assert(currentInput != null, "A current input resource is required"); Debug.Assert(inputParameter != null, "The input lambda parameter is required"); Debug.Assert(referencedInputs != null, "The referenced inputs list is required"); InputBinder binder = new InputBinder(currentInput, inputParameter); Expression result = binder.Visit(e); referencedInputs.AddRange(binder.referencedInputs); return result; }
/// <summary> /// Replaces Lambda parameter references or transparent scope property accesses over those Lambda /// parameter references with <see cref="InputReferenceExpression"/>s to the appropriate corresponding /// <see cref="QueryableResourceExpression"/>s, based on the 'input' QueryableResourceExpression to which the /// Lambda is logically applied and any enclosing transparent scope applied to that input resource. /// </summary> /// <param name="e">The expression to rebind</param> /// <param name="currentInput"> /// The 'current input' resource - either the root resource or the /// rightmost resource in the navigation chain.</param> /// <param name="inputParameter">The Lambda parameter that represents a reference to the 'input'</param> /// <param name="referencedInputs">A list that will be populated with the resources that were referenced by the rebound expression</param> /// <returns> /// The rebound version of <paramref name="e"/> where MemberExpression/ParameterExpressions that /// represent resource references have been replaced with appropriate InputReferenceExpressions. /// </returns> internal static Expression Bind(Expression e, ResourceExpression currentInput, ParameterExpression inputParameter, List <ResourceExpression> referencedInputs) { Debug.Assert(e != null, "Expression cannot be null"); Debug.Assert(currentInput != null, "A current input resource is required"); Debug.Assert(inputParameter != null, "The input lambda parameter is required"); Debug.Assert(referencedInputs != null, "The referenced inputs list is required"); InputBinder binder = new InputBinder(currentInput, inputParameter); Expression result = binder.Visit(e); referencedInputs.AddRange(binder.referencedInputs); return(result); }
internal LambdaExpression Rebind(LambdaExpression lambda, ResourceExpression source) { this.successfulRebind = true; this.oldLambdaParameter = lambda.Parameters[0]; this.projectionSource = source; Expression body = this.Visit(lambda.Body); if (this.successfulRebind) { Type delegateType = typeof(Func<,>).MakeGenericType(new Type[] { newLambdaParameter.Type, lambda.Body.Type }); return Expression.Lambda(delegateType, body, new ParameterExpression[] { this.newLambdaParameter }); } else { throw new NotSupportedException(Strings.ALinq_CanOnlyProjectTheLeaf); } }
internal static LambdaExpression TryToRewrite(LambdaExpression le, ResourceExpression source) { Type proposedParameterType = source.ResourceType; LambdaExpression result; if (!ResourceBinder.PatternRules.MatchSingleArgumentLambda(le, out le) || // can only rewrite single parameter Lambdas. ClientTypeUtil.TypeOrElementTypeIsEntity(le.Parameters[0].Type) || // only attempt to rewrite if lambda parameter is not an entity type !(le.Parameters[0].Type.GetProperties().Any(p => p.PropertyType == proposedParameterType))) // lambda parameter must have public property that is same as proposed type. { result = le; } else { ProjectionRewriter rewriter = new ProjectionRewriter(proposedParameterType); result = rewriter.Rebind(le, source); } return result; }
internal LambdaExpression Rebind(LambdaExpression lambda, ResourceExpression source) { this.successfulRebind = true; this.oldLambdaParameter = lambda.Parameters[0]; this.projectionSource = source; Expression body = this.Visit(lambda.Body); if (this.successfulRebind) { Type delegateType = typeof(Func <,>).MakeGenericType(new Type[] { newLambdaParameter.Type, lambda.Body.Type }); return(Expression.Lambda(delegateType, body, new ParameterExpression[] { this.newLambdaParameter })); } else { throw new NotSupportedException(Strings.ALinq_CanOnlyProjectTheLeaf); } }
internal static LambdaExpression TryToRewrite(LambdaExpression le, ResourceExpression source) { Type proposedParameterType = source.ResourceType; LambdaExpression result; if (!ResourceBinder.PatternRules.MatchSingleArgumentLambda(le, out le) || // can only rewrite single parameter Lambdas. ClientTypeUtil.TypeOrElementTypeIsEntity(le.Parameters[0].Type) || // only attempt to rewrite if lambda parameter is not an entity type !(le.Parameters[0].Type.GetProperties().Any(p => p.PropertyType == proposedParameterType))) // lambda parameter must have public property that is same as proposed type. { result = le; } else { ProjectionRewriter rewriter = new ProjectionRewriter(proposedParameterType); result = rewriter.Rebind(le, source); } return(result); }
/// <summary> /// Constructs a new InputBinder based on the specified input resources, which is represented by the specified ParameterExpression. /// </summary> /// <param name="resource">The current input resource from which valid references must start</param> /// <param name="setReferenceParam">The parameter that must be referenced in order to refer to the specified input resources</param> private InputBinder(ResourceExpression resource, ParameterExpression setReferenceParam) { this.input = resource; this.inputResource = resource as QueryableResourceExpression; this.inputParameter = setReferenceParam; }
/// <summary> /// Resolves member accesses that represent transparent scope property accesses to the corresponding resource, /// iff the input resource is enclosed in a transparent scope and the specified MemberExpression represents /// such a property access. /// </summary> /// <param name="m">MemberExpression expression to visit</param> /// <returns> /// An InputReferenceExpression if the member access represents a transparent scope property /// access that can be resolved to a resource in the path that produces the input resource; /// otherwise the same MemberExpression is returned. /// </returns> internal override Expression VisitMemberAccess(MemberExpression m) { // If the current input resource is not enclosed in a transparent scope, then this // MemberExpression cannot represent a valid transparent scope access based on the input parameter. if (this.inputResource == null || !this.inputResource.HasTransparentScope) { return(base.VisitMemberAccess(m)); } ParameterExpression innerParamRef = null; Stack <PropertyInfo> nestedAccesses = new Stack <PropertyInfo>(); MemberExpression memberRef = m; while (memberRef != null && PlatformHelper.IsProperty(memberRef.Member) && memberRef.Expression != null) { nestedAccesses.Push((PropertyInfo)memberRef.Member); if (memberRef.Expression.NodeType == ExpressionType.Parameter) { innerParamRef = (ParameterExpression)memberRef.Expression; } memberRef = memberRef.Expression as MemberExpression; } // Only continue if the inner non-MemberExpression is the input reference ParameterExpression and // at least one property reference is present - otherwise this cannot be a transparent scope access. if (innerParamRef != this.inputParameter || nestedAccesses.Count == 0) { return(m); } ResourceExpression target = this.input; QueryableResourceExpression targetResource = this.inputResource; bool transparentScopeTraversed = false; // Process all the traversals through transparent scopes. while (nestedAccesses.Count > 0) { if (targetResource == null || !targetResource.HasTransparentScope) { break; } // Peek the property; pop it once it's consumed // (it could be a non-transparent-identifier access). PropertyInfo currentProp = nestedAccesses.Peek(); // If this is the accessor for the target, then the member // refers to the target itself. if (currentProp.Name.Equals(targetResource.TransparentScope.Accessor, StringComparison.Ordinal)) { target = targetResource; nestedAccesses.Pop(); transparentScopeTraversed = true; continue; } // This member could also be one of the in-scope sources of the target. Expression source; if (!targetResource.TransparentScope.SourceAccessors.TryGetValue(currentProp.Name, out source)) { break; } transparentScopeTraversed = true; nestedAccesses.Pop(); Debug.Assert(source != null, "source != null -- otherwise ResourceBinder created an accessor to nowhere"); InputReferenceExpression sourceReference = source as InputReferenceExpression; if (sourceReference == null) { targetResource = source as QueryableResourceExpression; if (targetResource == null || !targetResource.HasTransparentScope) { target = (ResourceExpression)source; } } else { targetResource = sourceReference.Target as QueryableResourceExpression; target = targetResource; } } // If no traversals were made, the original expression is OK. if (!transparentScopeTraversed) { return(m); } // Process traversals after the transparent scope. Expression result = this.CreateReference(target); while (nestedAccesses.Count > 0) { result = Expression.Property(result, nestedAccesses.Pop()); } return(result); }
/// <summary> /// Returns an <see cref="InputReferenceExpression"/> that references the specified resource, /// and also adds the the resource to the hashset of resources that were referenced by the /// expression that is being rebound. /// </summary> /// <param name="resource">The resource for which a reference was found</param> /// <returns>An InputReferenceExpression that represents a reference to the specified resource</returns> private Expression CreateReference(ResourceExpression resource) { this.referencedInputs.Add(resource); return(resource.CreateReference()); }
/// <summary> /// Constructs a new input reference expression that refers to the specified resource set /// </summary> /// <param name="target">The target resource set that the new expression will reference</param> internal InputReferenceExpression(ResourceExpression target) { Debug.Assert(target != null, "Target resource set cannot be null"); this.target = target; }
/// <summary> /// Returns an <see cref="InputReferenceExpression"/> that references the specified resource, /// and also adds the the resource to the hashset of resources that were referenced by the /// expression that is being rebound. /// </summary> /// <param name="resource">The resource for which a reference was found</param> /// <returns>An InputReferenceExpression that represents a reference to the specified resource</returns> private Expression CreateReference(ResourceExpression resource) { this.referencedInputs.Add(resource); return resource.CreateReference(); }