/// <summary> /// QueryableResourceExpression visit method. /// </summary> /// <param name="rse">QueryableResourceExpression expression to visit</param> /// <returns>Visited QueryableResourceExpression expression</returns> internal override Expression VisitQueryableResourceExpression(QueryableResourceExpression rse) { if ((ResourceExpressionType)rse.NodeType == ResourceExpressionType.ResourceNavigationProperty) { if (rse.IsOperationInvocation && !(rse.Source is QueryableResourceExpression)) { var normalizerRewrites = new Dictionary <Expression, Expression>(ReferenceEqualityComparer <Expression> .Instance); var e = Evaluator.PartialEval(rse.Source); e = ExpressionNormalizer.Normalize(e, normalizerRewrites); e = ResourceBinder.Bind(e, this.context); this.Visit(e); } else { this.Visit(rse.Source); } this.uriBuilder.Append(UriHelper.FORWARDSLASH).Append(this.ExpressionToString(rse.MemberExpression, /*inPath*/ true)); } else if (rse.MemberExpression != null) { // this is a resource set expression // we should be at the very beginning of // the URI Debug.Assert(this.uriBuilder.Length == 0, "The builder is not empty while we are adding a resourceset"); string entitySetName = (String)((ConstantExpression)rse.MemberExpression).Value; this.uriBuilder.Append(this.context.BaseUriResolver.GetEntitySetUri(entitySetName)); } else { this.uriBuilder.Append(this.context.BaseUriResolver.BaseUriOrNull); } WebUtil.RaiseVersion(ref this.uriVersion, rse.UriVersion); if (rse.ResourceTypeAs != null) { this.uriBuilder.Append(UriHelper.FORWARDSLASH); UriHelper.AppendTypeSegment(this.uriBuilder, rse.ResourceTypeAs, this.context, /*inPath*/ true, ref this.uriVersion); } if (rse.KeyPredicateConjuncts.Count > 0) { this.context.UrlKeyDelimiter.AppendKeyExpression(rse.GetKeyProperties(), kvp => ClientTypeUtil.GetServerDefinedName(kvp.Key), kvp => kvp.Value.Value, this.uriBuilder); } if (rse.IsOperationInvocation) { this.VisitOperationInvocation(rse); } if (rse.CountOption == CountOption.CountSegment) { // append $count segment: /$count this.uriBuilder.Append(UriHelper.FORWARDSLASH).Append(UriHelper.DOLLARSIGN).Append(UriHelper.COUNT); } this.VisitQueryOptions(rse); return(rse); }
/// <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> /// Creates a navigation resource expression /// </summary> /// <param name="expressionType">The expression type.</param> /// <param name="type">the return type of the expression</param> /// <param name="source">the source expression</param> /// <param name="memberExpression">property member name</param> /// <param name="resourceType">the element type of the resource</param> /// <param name="expandPaths">expand paths for resource set</param> /// <param name="countOption">count query option for the resource</param> /// <param name="customQueryOptions">custom query options for resource</param> /// <param name="projection">the projection expression</param> /// <param name="resourceTypeAs">TypeAs type</param> /// <param name="uriVersion">version of the Uri from the expand and projection paths</param> /// <param name="operationName">The operation name.</param> /// <param name="operationParameters">The operation parameter names and parameters pair for Resource</param> /// <returns>The navigation resource expression.</returns> internal static QueryableResourceExpression CreateNavigationResourceExpression(ExpressionType expressionType, Type type, Expression source, Expression memberExpression, Type resourceType, List <string> expandPaths, CountOption countOption, Dictionary <ConstantExpression, ConstantExpression> customQueryOptions, ProjectionQueryOptionExpression projection, Type resourceTypeAs, Version uriVersion, string operationName, Dictionary <string, string> operationParameters) { Debug.Assert( expressionType == (ExpressionType)ResourceExpressionType.RootResourceSet || expressionType == (ExpressionType)ResourceExpressionType.ResourceNavigationProperty || expressionType == (ExpressionType)ResourceExpressionType.RootSingleResource, "Expression type is not one of following: RootResourceSet, ResourceNavigationProperty, RootSingleResource."); QueryableResourceExpression expression = null; if (expressionType == (ExpressionType)ResourceExpressionType.RootResourceSet || expressionType == (ExpressionType)ResourceExpressionType.ResourceNavigationProperty) { expression = new ResourceSetExpression(type, source, memberExpression, resourceType, expandPaths, countOption, customQueryOptions, projection, resourceTypeAs, uriVersion); } if (expressionType == (ExpressionType)ResourceExpressionType.RootSingleResource) { expression = new SingletonResourceExpression(type, source, memberExpression, resourceType, expandPaths, countOption, customQueryOptions, projection, resourceTypeAs, uriVersion); } if (expression != null) { expression.OperationName = operationName; expression.OperationParameters = operationParameters; return(expression); } return(null); }
/// <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> /// Visit Function Invocation /// </summary> /// <param name="rse">Resource Expression with function invocation</param> internal void VisitOperationInvocation(QueryableResourceExpression rse) { if (!this.uriBuilder.ToString().EndsWith(UriHelper.FORWARDSLASH.ToString(), StringComparison.Ordinal)) { this.uriBuilder.Append(UriHelper.FORWARDSLASH); } if (rse.IsOperationInvocation) { this.uriBuilder.Append(rse.OperationName); if (rse.IsAction) { return; } this.uriBuilder.Append(UriHelper.LEFTPAREN); bool needComma = false; KeyValuePair <string, string>[] parameters = rse.OperationParameters.ToArray(); for (int i = 0; i < parameters.Length; ++i) { KeyValuePair <string, string> param = parameters[i]; if (needComma) { this.uriBuilder.Append(UriHelper.COMMA); } this.uriBuilder.Append(param.Key); this.uriBuilder.Append(UriHelper.EQUALSSIGN); // non-primitive value, use alias. if (!UriHelper.IsPrimitiveValue(param.Value)) { string aliasName = UriHelper.ATSIGN + param.Key; int count = 1; while (this.alias.ContainsKey(aliasName)) { aliasName = UriHelper.ATSIGN + param.Key + count; count++; } this.uriBuilder.Append(aliasName); this.alias.Add(aliasName, param.Value); } else { // primitive value, do not use alias. this.uriBuilder.Append(param.Value); } needComma = true; } this.uriBuilder.Append(UriHelper.RIGHTPAREN); } }
/// <summary> /// QueryableResourceExpression visit method. /// </summary> /// <param name="rse">QueryableResource expression to visit</param> /// <returns>Visited QueryableResourceExpression expression</returns> internal virtual Expression VisitQueryableResourceExpression(QueryableResourceExpression rse) { Expression source = this.Visit(rse.Source); if (source != rse.Source) { rse = QueryableResourceExpression.CreateNavigationResourceExpression(rse.NodeType, rse.Type, source, rse.MemberExpression, rse.ResourceType, rse.ExpandPaths, rse.CountOption, rse.CustomQueryOptions, rse.Projection, rse.ResourceTypeAs, rse.UriVersion); } return(rse); }
/// <summary> /// QueryableResourceExpression visit method. /// </summary> /// <param name="rse">QueryableResource expression to visit</param> /// <returns>Visited QueryableResourceExpression expression</returns> internal virtual Expression VisitQueryableResourceExpression(QueryableResourceExpression rse) { Expression source = this.Visit(rse.Source); if (source != rse.Source) { rse = QueryableResourceExpression.CreateNavigationResourceExpression(rse.NodeType, rse.Type, source, rse.MemberExpression, rse.ResourceType, rse.ExpandPaths, rse.CountOption, rse.CustomQueryOptions, rse.Projection, rse.ResourceTypeAs, rse.UriVersion, rse.OperationName, rse.OperationParameters); } return rse; }
/// <summary> /// Instructs this resource set expression to use the input reference expression from <paramref name="newInput"/> as it's /// own input reference, and to retarget the input reference from <paramref name="newInput"/> to this resource set expression. /// </summary> /// <param name="newInput">The resource set expression from which to take the input reference.</param> /// <remarks>Used exclusively by ResourceBinder.RemoveTransparentScope.</remarks> internal void OverrideInputReference(QueryableResourceExpression newInput) { Debug.Assert(newInput != null, "Original resource set cannot be null"); Debug.Assert(this.inputRef == null, "OverrideInputReference cannot be called if the target has already been referenced"); InputReferenceExpression inputRef = newInput.inputRef; if (inputRef != null) { this.inputRef = inputRef; inputRef.OverrideTarget(this); } }
/// <summary> /// Create a clone with new type. /// </summary> /// <param name="type">The type.</param> /// <returns>The new clone.</returns> internal override ResourceExpression CreateCloneWithNewType(Type type) { QueryableResourceExpression clone = this.CreateCloneWithNewTypes(type, TypeSystem.GetElementType(type)); if (this.keyPredicateConjuncts != null && this.keyPredicateConjuncts.Count > 0) { clone.SetKeyPredicate(this.keyPredicateConjuncts); } clone.keyFilter = this.keyFilter; clone.sequenceQueryOptions = this.sequenceQueryOptions; clone.transparentScope = this.transparentScope; return(clone); }
/// <summary> /// Create a clone of the ResourceExpression. /// </summary> /// <returns>The new clone.</returns> internal override ResourceExpression CreateCloneResourceExpression() { QueryableResourceExpression clone = this.CreateClone(); if (this.keyPredicateConjuncts != null && this.keyPredicateConjuncts.Count > 0) { clone.SetKeyPredicate(this.keyPredicateConjuncts); } clone.keyFilter = this.keyFilter; clone.sequenceQueryOptions = this.sequenceQueryOptions; clone.transparentScope = this.transparentScope; return(clone); }
internal override Expression VisitMemberAccess(MemberExpression m) { if (m.Expression == this.oldLambdaParameter) { // Member is only a valid projection target if it is the target of the current scope QueryableResourceExpression resourceExpression = this.projectionSource as QueryableResourceExpression; if (resourceExpression != null && resourceExpression.HasTransparentScope && resourceExpression.TransparentScope.Accessor == m.Member.Name) { Debug.Assert(m.Type == this.newLambdaParameter.Type, "Should not be rewriting a parameter with a different type than the original"); return(this.newLambdaParameter); } else { this.successfulRebind = false; } } return(base.VisitMemberAccess(m)); }
/// <summary> /// Visit Function Invocation /// </summary> /// <param name="rse">Resource Expression with function invocation</param> internal void VisitOperationInvocation(QueryableResourceExpression rse) { if (!this.uriBuilder.ToString().EndsWith(UriHelper.FORWARDSLASH.ToString(), StringComparison.Ordinal)) { this.uriBuilder.Append(UriHelper.FORWARDSLASH); } if (rse.IsOperationInvocation) { bool isOperationImport = rse.MemberExpression == null; string operationName = isOperationImport ? rse.OperationName : (rse.OperationParameters.Last().Value + "." + rse.OperationName); this.uriBuilder.Append(operationName); if (rse.IsAction) { return; } this.uriBuilder.Append(UriHelper.LEFTPAREN); bool needComma = false; KeyValuePair <string, string>[] parameters = rse.OperationParameters.ToArray(); for (int i = 0; i < parameters.Length - (isOperationImport ? 0 : 1); ++i) { KeyValuePair <string, string> param = parameters[i]; if (needComma) { this.uriBuilder.Append(UriHelper.COMMA); } this.uriBuilder.Append(param.Key); this.uriBuilder.Append(UriHelper.EQUALSSIGN); this.uriBuilder.Append(param.Value); needComma = true; } this.uriBuilder.Append(UriHelper.RIGHTPAREN); } }
/// <summary> /// Cast QueryableResourceExpression to new type without affecting member type /// </summary> /// <param name="type">The new expression type</param> /// <returns>A copy of this with the new types</returns> internal QueryableResourceExpression CreateCloneForTransparentScope(Type type) { // QueryableResourceExpression 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 newType = typeof(IOrderedQueryable <>).MakeGenericType(elementType); QueryableResourceExpression clone = this.CreateCloneWithNewTypes(newType, this.ResourceType); if (this.keyPredicateConjuncts != null && this.keyPredicateConjuncts.Count > 0) { clone.SetKeyPredicate(this.keyPredicateConjuncts); } clone.keyFilter = this.keyFilter; clone.sequenceQueryOptions = this.sequenceQueryOptions; clone.transparentScope = this.transparentScope; return(clone); }
/// <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> /// Visit Function Invocation /// </summary> /// <param name="rse">Resource Expression with function invocation</param> internal void VisitOperationInvocation(QueryableResourceExpression rse) { if (!this.uriBuilder.ToString().EndsWith(UriHelper.FORWARDSLASH.ToString(), StringComparison.Ordinal)) { this.uriBuilder.Append(UriHelper.FORWARDSLASH); } if (rse.IsOperationInvocation) { this.uriBuilder.Append(rse.OperationName); if (rse.IsAction) { return; } this.uriBuilder.Append(UriHelper.LEFTPAREN); bool needComma = false; KeyValuePair<string, string>[] parameters = rse.OperationParameters.ToArray(); for (int i = 0; i < parameters.Length; ++i) { KeyValuePair<string, string> param = parameters[i]; if (needComma) { this.uriBuilder.Append(UriHelper.COMMA); } this.uriBuilder.Append(param.Key); this.uriBuilder.Append(UriHelper.EQUALSSIGN); // non-primitive value, use alias. if (!UriHelper.IsPrimitiveValue(param.Value)) { string aliasName = UriHelper.ATSIGN + param.Key; int count = 1; while (this.alias.ContainsKey(aliasName)) { aliasName = UriHelper.ATSIGN + param.Key + count; count++; } this.uriBuilder.Append(aliasName); this.alias.Add(aliasName, param.Value); } else { // primitive value, do not use alias. this.uriBuilder.Append(param.Value); } needComma = true; } this.uriBuilder.Append(UriHelper.RIGHTPAREN); } }
/// <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> /// QueryableResourceExpression visit method. /// </summary> /// <param name="rse">QueryableResourceExpression expression to visit</param> /// <returns>Visited QueryableResourceExpression expression</returns> internal override Expression VisitQueryableResourceExpression(QueryableResourceExpression rse) { if ((ResourceExpressionType)rse.NodeType == ResourceExpressionType.ResourceNavigationProperty) { if (rse.IsOperationInvocation && !(rse.Source is QueryableResourceExpression)) { var normalizerRewrites = new Dictionary<Expression, Expression>(ReferenceEqualityComparer<Expression>.Instance); var e = Evaluator.PartialEval(rse.Source); e = ExpressionNormalizer.Normalize(e, normalizerRewrites); e = ResourceBinder.Bind(e, this.context); this.Visit(e); } else { this.Visit(rse.Source); } this.uriBuilder.Append(UriHelper.FORWARDSLASH).Append(this.ExpressionToString(rse.MemberExpression, /*inPath*/ true)); } else if (rse.MemberExpression != null) { // this is a resource set expression // we should be at the very begining of // the URI Debug.Assert(this.uriBuilder.Length == 0, "The builder is not empty while we are adding a resourset"); string entitySetName = (String)((ConstantExpression)rse.MemberExpression).Value; this.uriBuilder.Append(this.context.BaseUriResolver.GetEntitySetUri(entitySetName)); } else { this.uriBuilder.Append(this.context.BaseUriResolver.BaseUriOrNull); } WebUtil.RaiseVersion(ref this.uriVersion, rse.UriVersion); if (rse.ResourceTypeAs != null) { this.uriBuilder.Append(UriHelper.FORWARDSLASH); UriHelper.AppendTypeSegment(this.uriBuilder, rse.ResourceTypeAs, this.context, /*inPath*/ true, ref this.uriVersion); } if (rse.KeyPredicateConjuncts.Count > 0) { this.context.UrlConventions.AppendKeyExpression(rse.GetKeyProperties(), kvp => ClientTypeUtil.GetServerDefinedName(kvp.Key), kvp => kvp.Value.Value, this.uriBuilder); } if (rse.IsOperationInvocation) { this.VisitOperationInvocation(rse); } if (rse.CountOption == CountOption.CountSegment) { // append $count segment: /$count this.uriBuilder.Append(UriHelper.FORWARDSLASH).Append(UriHelper.DOLLARSIGN).Append(UriHelper.COUNT); } this.VisitQueryOptions(rse); return rse; }