RequestQueryProcessor.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / fx / src / DataWeb / Server / System / Data / Services / RequestQueryProcessor.cs / 1625574 / RequestQueryProcessor.cs

                            //---------------------------------------------------------------------- 
// 
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// 
//  
//      Provides a class capable of processing query arguments.
//  
// 
// @owner  [....]
//--------------------------------------------------------------------- 

namespace System.Data.Services
{
    using System; 
    using System.Collections;
    using System.Collections.Generic; 
    using System.Data.Services.Client; 
    using System.Data.Services.Internal;
    using System.Data.Services.Parsing; 
    using System.Data.Services.Providers;
    using System.Diagnostics;
    using System.Globalization;
    using System.Linq; 
    using System.Linq.Expressions;
    using System.Reflection; 
    using System.Text; 

    /// Use this class to process a web data service request URI. 
    internal struct RequestQueryProcessor
    {
        #region Private fields.
 
        /// MethodInfo for Skip.
        private static readonly MethodInfo InvokeSkipMethodInfo = typeof(RequestQueryProcessor).GetMethod("InvokeSkip", BindingFlags.NonPublic | BindingFlags.Static); 
 
        /// MethodInfo for Take.
        private static readonly MethodInfo InvokeTakeMethodInfo = typeof(RequestQueryProcessor).GetMethod("InvokeTake", BindingFlags.NonPublic | BindingFlags.Static); 

        /// The generic method for CountQueryResult'[T]()
        private static readonly MethodInfo countQueryResultMethodInfo = typeof(RequestQueryProcessor).GetMethod("CountQueryResult", BindingFlags.NonPublic | BindingFlags.Static);
 
        /// Original description over which query composition takes place.
        private readonly RequestDescription description; 
 
        /// Whether the $filter query option can be applied to the request.
        private readonly bool filterQueryApplicable; 

        /// Service with data and configuration.
        private readonly IDataService service;
 
        /// Whether the $orderby, $skip, $take and $count options can be applied to the request.
        private readonly bool setQueryApplicable; 
 
        /// Whether the top level request is a candidate for paging.
        private readonly bool pagingApplicable; 

        /// Has custom paging already been applied?
        private bool appliedCustomPaging;
 
        /// List of paths to be expanded.
        private List expandPaths; 
 
        /// List of paths to be expanded, before resolving the identifiers
        private List> expandPathsAsText; 

        /// Root projection segment of the tree specifying projections and expansions for the query.
        private RootProjectionNode rootProjectionNode;
 
        /// Parser used for parsing ordering expressions
        private RequestQueryParser.ExpressionParser orderingParser; 
 
        /// Collection of ordering expressions for the current query
        private OrderingInfo topLevelOrderingInfo; 

        /// Whether any order has been applied.
        private bool orderApplied;
 
        /// Value of $skip argument
        private int? skipCount; 
 
        /// Value of $top argument
        private int? topCount; 

        /// Query being composed.
        private IQueryable query;
 
        /// Query results to be returned.
        private IEnumerable queryResults; 
 
        #endregion Private fields.
 
        /// Initializes a new  instance.
        /// Service with data and configuration.
        /// Description for request processed so far.
        private RequestQueryProcessor(IDataService service, RequestDescription description) 
        {
            this.service = service; 
            this.description = description; 
            this.orderApplied = false;
            this.skipCount = null; 
            this.topCount = null;
            this.query = (IQueryable)description.RequestEnumerable;

            this.filterQueryApplicable = 
                description.TargetKind == RequestTargetKind.Resource ||
                description.TargetKind == RequestTargetKind.OpenProperty || 
                description.TargetKind == RequestTargetKind.ComplexObject || 
                (description.CountOption == RequestQueryCountOption.ValueOnly);
 
            this.setQueryApplicable = (description.TargetKind == RequestTargetKind.Resource && !description.IsSingleResult) ||
                                       description.CountOption == RequestQueryCountOption.ValueOnly;

            // Server Driven Paging is not considered for the following cases: 
            // 1. Top level result is not or resource type or it is a single valued result.
            // 2. $count segment provided. 
            // 3. Non-GET requests do not honor SDP. 
            // 4. Only exception for Non-GET requests is if the request is coming from a Service
            //    operation that returns a set of result values of entity type. 
            this.pagingApplicable = (description.TargetKind == RequestTargetKind.Resource && !description.IsSingleResult) &&
                                    (description.CountOption != RequestQueryCountOption.ValueOnly) &&
                                    !description.IsRequestForEnumServiceOperation &&
                                    (service.OperationContext.Host.AstoriaHttpVerb == AstoriaVerbs.GET || description.SegmentInfos[0].TargetSource == RequestTargetSource.ServiceOperation); 

            this.appliedCustomPaging = false; 
 
            this.expandPaths = null;
            this.expandPathsAsText = null; 
            this.rootProjectionNode = null;
            this.orderingParser = null;
            this.topLevelOrderingInfo = null;
            this.queryResults = null; 
        }
 
        ///  
        /// Is the top level container for the query paged i.e. we need to use StandardPaging.
        ///  
        private bool IsStandardPaged
        {
            get
            { 
                if (this.pagingApplicable && !this.IsCustomPaged)
                { 
                    ResourceSetWrapper targetContainer = this.description.LastSegmentInfo.TargetContainer; 
                    Debug.Assert(targetContainer != null, "Must have target container for non-open properties");
                    return targetContainer.PageSize != 0; 
                }
                else
                {
                    // Target container is null for OpenProperties and we have decided not to 
                    // to enable paging for OpenType scenarios
                    return false; 
                } 
            }
        } 

        /// Do we need to use CustomPaging for this service.
        private bool IsCustomPaged
        { 
            get
            { 
                return this.service.PagingProvider.IsCustomPagedForQuery; 
            }
        } 

        /// Checks that no query arguments were sent in the request.
        /// Service to check.
        /// true if only V2 query parameters must be checked, otherwise all the query parameters will be checked. 
        /// 
        /// Regular processing checks argument applicability, but for 
        /// service operations that return an IEnumerable this is part 
        /// of the contract on service operations, rather than a structural
        /// check on the request. 
        /// 
        internal static void CheckEmptyQueryArguments(IDataService service, bool checkForOnlyV2QueryParameters)
        {
            Debug.Assert(service != null, "service != null"); 

            DataServiceHostWrapper host = service.OperationContext.Host; 
            if ((!checkForOnlyV2QueryParameters && 
                 (!String.IsNullOrEmpty(host.GetQueryStringItem(XmlConstants.HttpQueryStringExpand)) ||
                  !String.IsNullOrEmpty(host.GetQueryStringItem(XmlConstants.HttpQueryStringFilter)) || 
                  !String.IsNullOrEmpty(host.GetQueryStringItem(XmlConstants.HttpQueryStringOrderBy)) ||
                  !String.IsNullOrEmpty(host.GetQueryStringItem(XmlConstants.HttpQueryStringSkip)) ||
                  !String.IsNullOrEmpty(host.GetQueryStringItem(XmlConstants.HttpQueryStringTop)))) ||
                !String.IsNullOrEmpty(host.GetQueryStringItem(XmlConstants.HttpQueryStringInlineCount)) || 
                !String.IsNullOrEmpty(host.GetQueryStringItem(XmlConstants.HttpQueryStringSelect)) ||
                !String.IsNullOrEmpty(host.GetQueryStringItem(XmlConstants.HttpQueryStringSkipToken))) 
            { 
                // 400: Bad Request
                throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QueryNoOptionsApplicable); 
            }
        }

        /// Checks that no query arguments were sent in the request. 
        /// Service to check.
        ///  
        /// Regular processing checks argument applicability, but for 
        /// service operations that return an IEnumerable this is part
        /// of the contract on service operations, rather than a structural 
        /// check on the request.
        /// 
        internal static void CheckV2EmptyQueryArguments(IDataService service)
        { 
            Debug.Assert(service != null, "service != null");
            CheckEmptyQueryArguments(service, service.Provider.IsV1Provider); 
        } 

        ///  
        /// Composes a property navigation with the appropriate filter lamba, as appropriate.
        /// 
        /// Member access expression to compose.
        /// Lambda expression used for the filter. 
        /// Whether null propagation is required on the .
        /// The composed expression. 
        internal static Expression ComposePropertyNavigation( 
            Expression expression,
            LambdaExpression filterLambda, 
            bool propagateNull)
        {
            Debug.Assert(expression != null, "expression != null");
            Debug.Assert(filterLambda != null, "filterLambda != null"); 

            Expression fixedFilter = ParameterReplacerVisitor.Replace( 
                    filterLambda.Body, 
                    filterLambda.Parameters[0],
                    expression); 

            Expression test;
            if (propagateNull)
            { 
                Expression nullConstant = WebUtil.NullLiteral;
                test = Expression.AndAlso(Expression.NotEqual(expression, nullConstant), fixedFilter); 
            } 
            else
            { 
                test = fixedFilter;
            }

            Expression conditionTrue = expression; 
            Expression conditionFalse = Expression.Constant(null, conditionTrue.Type);
            return Expression.Condition(test, conditionTrue, conditionFalse); 
        } 

        ///  
        /// Processes query arguments and returns a request description for
        /// the resulting query.
        /// 
        /// Service with data and configuration information. 
        /// Description for request processed so far.
        /// A new . 
        internal static RequestDescription ProcessQuery(IDataService service, RequestDescription description) 
        {
            Debug.Assert(service != null, "service != null"); 

            // In some cases, like CUD operations, we do not want to allow any query parameters to be specified.
            // But in V1, we didn't have this check hence we cannot fix this now. But we need to check only for
            // V2 query parameters and stop them 
            if ((service.OperationContext.Host.AstoriaHttpVerb != AstoriaVerbs.GET) &&
                description.SegmentInfos[0].TargetSource != RequestTargetSource.ServiceOperation) 
            { 
                RequestQueryProcessor.CheckV2EmptyQueryArguments(service);
            } 

            // When the request doesn't produce an IQueryable result,
            // we can short-circuit all further processing.
            if (!(description.RequestEnumerable is IQueryable)) 
            {
                RequestQueryProcessor.CheckEmptyQueryArguments(service, false /*checkForOnlyV2QueryParameters*/); 
                return description; 
            }
            else 
            {
                return new RequestQueryProcessor(service, description).ProcessQuery();
            }
        } 

        /// Generic method to invoke a Skip method on an IQueryable source. 
        /// Element type of the source. 
        /// Source query.
        /// Element count to skip. 
        /// A new query that skips  elements of .
        private static IQueryable InvokeSkip(IQueryable source, int count)
        {
            Debug.Assert(source != null, "source != null"); 
            IQueryable typedSource = (IQueryable)source;
            return Queryable.Skip(typedSource, count); 
        } 

        /// Generic method to invoke a Take method on an IQueryable source. 
        /// Element type of the source.
        /// Source query.
        /// Element count to take.
        /// A new query that takes  elements of . 
        private static IQueryable InvokeTake(IQueryable source, int count)
        { 
            Debug.Assert(source != null, "source != null"); 
            IQueryable typedSource = (IQueryable)source;
            return Queryable.Take(typedSource, count); 
        }

        /// Count the query result before $top and $skip are applied
        /// Element type of the source 
        /// Source query
        /// The count from the provider 
        private static long CountQueryResult(IQueryable query) 
        {
            return Queryable.LongCount(query); 
        }

        /// Reads an $expand or $select clause.
        /// Value to read. 
        /// True if we're parsing $select clause False for $expand
        /// A list of paths, each of which is a list of identifiers. 
        /// Same method is used for both $expand and $select as the syntax is almost identical. 
        /// $select allows * at the end of the path while $expand doesn't.
        private static List> ReadExpandOrSelect(string value, bool select) 
        {
            Debug.Assert(!String.IsNullOrEmpty(value), "!String.IsNullOrEmpty(value)");

            List> result = new List>(); 
            List currentPath = null;
            ExpressionLexer lexer = new ExpressionLexer(value); 
            while (lexer.CurrentToken.Id != TokenId.End) 
            {
                string identifier; 
                bool lastSegment = false;
                if (select && lexer.CurrentToken.Id == TokenId.Star)
                {
                    identifier = lexer.CurrentToken.Text; 
                    lexer.NextToken();
                    lastSegment = true; 
                } 
                else
                { 
                    identifier = lexer.ReadDottedIdentifier();
                }

                if (currentPath == null) 
                {
                    currentPath = new List(); 
                    result.Add(currentPath); 
                }
 
                currentPath.Add(identifier);

                // Check whether we're at the end, whether we're drilling in,
                // or whether we're finishing with this path. 
                TokenId tokenId = lexer.CurrentToken.Id;
                if (tokenId != TokenId.End) 
                { 
                    if (lastSegment || tokenId != TokenId.Slash)
                    { 
                        lexer.ValidateToken(TokenId.Comma);
                        currentPath = null;
                    }
 
                    lexer.NextToken();
                } 
            } 

            return result; 
        }

        /// Gets the root projection node or creates one if no one exists yet.
        /// The root node of the projection tree. 
        private RootProjectionNode GetRootProjectionNode()
        { 
            if (this.rootProjectionNode == null) 
            {
                // Build the root of the projection and expansion tree 
                this.rootProjectionNode = new RootProjectionNode(
                    this.description.LastSegmentInfo.TargetContainer,
                    this.topLevelOrderingInfo,
                    null, 
                    this.skipCount,
                    this.topCount, 
                    null, 
                    this.expandPaths,
                    this.description.TargetResourceType); 
            }

            return this.rootProjectionNode;
        } 

        /// Checks and resolved all textual expand paths and removes unnecessary paths. 
        private void CheckExpandPaths() 
        {
            Debug.Assert(this.expandPathsAsText != null, "this.expandPaths != null"); 
            if (this.expandPathsAsText.Count > 0 && this.query == null)
            {
                throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QueryExpandOptionNotApplicable);
            } 

            this.expandPaths = new List(this.expandPathsAsText.Count); 
 
            for (int i = this.expandPathsAsText.Count - 1; i >= 0; i--)
            { 
                ResourceType resourceType = this.description.TargetResourceType;
                ResourceSetWrapper resourceSet = this.description.LastSegmentInfo.TargetContainer;

                List path = this.expandPathsAsText[i]; 
                ExpandSegmentCollection segments = new ExpandSegmentCollection(path.Count);
                bool ignorePath = false; 
                for (int j = 0; j < path.Count; j++) 
                {
                    string pathSegment = path[j]; 
                    ResourceProperty property = resourceType.TryResolvePropertyName(pathSegment);

                    if (property == null)
                    { 
                        if (resourceType.IsOpenType)
                        { 
                            // Open navigation properties are not supported on OpenTypes. 
                            throw DataServiceException.CreateBadRequestError(Strings.OpenNavigationPropertiesNotSupportedOnOpenTypes(pathSegment));
                        } 

                        throw DataServiceException.CreateSyntaxError(Strings.RequestUriProcessor_PropertyNotFound(resourceType.FullName, pathSegment));
                    }
 
                    if (property.TypeKind == ResourceTypeKind.EntityType)
                    { 
                        Expression filter = null; 
                        resourceSet = this.service.Provider.GetContainer(resourceSet, resourceType, property);
                        if (resourceSet == null) 
                        {
                            throw DataServiceException.CreateBadRequestError(Strings.BadRequest_InvalidPropertyNameSpecified(property.Name, resourceType.FullName));
                        }
 
                        resourceType = resourceSet.ResourceType;
 
                        bool singleResult = property.Kind == ResourcePropertyKind.ResourceReference; 
                        DataServiceConfiguration.CheckResourceRightsForRead(resourceSet, singleResult);
                        filter = DataServiceConfiguration.ComposeQueryInterceptors(this.service, resourceSet); 

                        if (resourceSet.PageSize != 0 && !singleResult && !this.IsCustomPaged)
                        {
                            OrderingInfo internalOrderingInfo = new OrderingInfo(true); 
                            ParameterExpression p = Expression.Parameter(resourceType.InstanceType, "p");
                            foreach (var keyProp in resourceType.KeyProperties) 
                            { 
                                Expression e;
                                if (keyProp.CanReflectOnInstanceTypeProperty) 
                                {
                                    e = Expression.Property(p, resourceType.GetPropertyInfo(keyProp));
                                }
                                else 
                                {
                                    // object LateBoundMethods.GetValue(object, ResourceProperty) 
                                    e = Expression.Call( 
                                                null, /*instance*/
                                                DataServiceProviderMethods.GetValueMethodInfo, 
                                                p,
                                                Expression.Constant(keyProp));
                                    e = Expression.Convert(e, keyProp.Type);
                                } 

                                internalOrderingInfo.Add(new OrderingExpression(Expression.Lambda(e, p), true)); 
                            } 

                            segments.Add(new ExpandSegment(property.Name, filter, resourceSet.PageSize, resourceSet, property, internalOrderingInfo)); 

                            // Paged expanded results force the response version to be 2.0
                            this.description.RaiseResponseVersion(2, 0);
                        } 
                        else
                        { 
                            // Expansion of collection could result in custom paging provider giving next link, so we need to set the null continuation token. 
                            if (!singleResult && this.IsCustomPaged)
                            { 
                                this.CheckAndApplyCustomPaging(null);
                            }

                            segments.Add(new ExpandSegment(property.Name, filter, this.service.Configuration.MaxResultsPerCollection, resourceSet, property, null)); 
                        }
 
                        // We need to explicitly update the feature version here since we may not bump response version 
                        this.description.UpdateAndCheckEpmFeatureVersion(resourceSet, this.service);
 
                        // The expanded resource type may have friendly feeds. Update version of the response accordingly
                        //
                        // Note the response DSV is payload specific and since for GET we won't know if the instances to be
                        // serialized will contain any properties with FF KeepInContent=false until serialization time which 
                        // happens after the headers are written, the best we can do is to determin this at the set level.
                        this.description.UpdateEpmResponseVersion(this.service.OperationContext.Host.RequestAccept, resourceSet, this.service.Provider); 
 
                        ignorePath = false;
                    } 
                    else
                    {
                        ignorePath = true;
                    } 
                }
 
                // Even though the path might be correct, we might need to 
                // ignore because it's a primitive.
                if (ignorePath) 
                {
                    this.expandPathsAsText.RemoveAt(i);
                }
                else 
                {
                    this.expandPaths.Add(segments); 
 
                    // And now build the projection and expansion tree nodes for this expand path as well
                    ExpandedProjectionNode currentNode = this.GetRootProjectionNode(); 
                    for (int j = 0; j < segments.Count; j++)
                    {
                        ExpandSegment segment = segments[j];
                        ExpandedProjectionNode childNode = (ExpandedProjectionNode)currentNode.FindNode(segment.Name); 
                        if (childNode == null)
                        { 
                            childNode = new ExpandedProjectionNode( 
                                segment.Name,
                                segment.ExpandedProperty, 
                                segment.Container,
                                segment.OrderingInfo,
                                segment.Filter,
                                null, 
                                segment.Container.PageSize != 0 ? (int?)segment.Container.PageSize : null,
                                segment.MaxResultsExpected != Int32.MaxValue ? (int?)segment.MaxResultsExpected : null); 
                            currentNode.AddNode(childNode); 
                        }
 
                        currentNode = childNode;
                    }

                    this.GetRootProjectionNode().ExpansionsSpecified = true; 
                }
            } 
        } 

        /// Checks that the query option is applicable to this request. 
        private void CheckFilterQueryApplicable()
        {
            if (!this.filterQueryApplicable)
            { 
                throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QueryFilterOptionNotApplicable);
            } 
        } 

        /// Checks that set query options are applicable to this request. 
        private void CheckSetQueryApplicable()
        {
            if (!this.setQueryApplicable)
            { 
                throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QuerySetOptionsNotApplicable);
            } 
        } 

        /// Processes the $expand argument of the request. 
        private void ProcessExpand()
        {
            string expand = this.service.OperationContext.Host.GetQueryStringItem(XmlConstants.HttpQueryStringExpand);
            if (!String.IsNullOrEmpty(expand)) 
            {
                this.expandPathsAsText = ReadExpandOrSelect(expand, false); 
            } 
            else
            { 
                this.expandPathsAsText = new List>();
            }

            this.CheckExpandPaths(); 
            this.service.InternalApplyingExpansions(this.query, this.expandPaths);
        } 
 
        /// Builds the tree of  to represent the $select query option.
        /// The value of the $select query option parsed into a list of lists. 
        /// This method assumes that $expand was already processed. And we have the tree
        /// of  objects for the $expand query option already built.
        private void ApplyProjectionsToExpandTree(List> selectPathsAsText)
        { 
            for (int i = selectPathsAsText.Count - 1; i >= 0; i--)
            { 
                List path = selectPathsAsText[i]; 
                ExpandedProjectionNode currentNode = this.GetRootProjectionNode();
                Debug.Assert(currentNode.ResourceType == this.description.TargetResourceType, "The resource type of the root doesn't match the target type of the query."); 

                for (int j = 0; j < path.Count; j++)
                {
                    Debug.Assert(currentNode != null, "Can't apply projections to non-expanded nodes."); 
                    string pathSegment = path[j];
                    bool lastPathSegment = (j == path.Count - 1); 
 
                    currentNode.ProjectionFound = true;
 
                    // '*' is special, it means "Project all immediate properties on this level."
                    if (pathSegment == "*")
                    {
                        Debug.Assert(lastPathSegment, "The * select segment must be the last one. This should have been checked already by the ReadExpandOrSelect method."); 
                        currentNode.ProjectAllImmediateProperties = true;
                        break; 
                    } 

                    ResourceType currentResourceType = currentNode.ResourceType; 
                    ResourceProperty property = currentResourceType.TryResolvePropertyName(pathSegment);
                    if (property == null)
                    {
                        if (!currentResourceType.IsOpenType) 
                        {
                            throw DataServiceException.CreateSyntaxError(Strings.RequestUriProcessor_PropertyNotFound(currentResourceType.FullName, pathSegment)); 
                        } 

                        if (!lastPathSegment) 
                        {
                            // Open navigation properties are not supported on OpenTypes.
                            throw DataServiceException.CreateBadRequestError(Strings.OpenNavigationPropertiesNotSupportedOnOpenTypes(pathSegment));
                        } 
                    }
                    else 
                    { 
                        switch (property.TypeKind)
                        { 
                            case ResourceTypeKind.Primitive:
                                if (!lastPathSegment)
                                {
                                    throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_PrimitivePropertyUsedAsNavigationProperty(currentResourceType.FullName, pathSegment)); 
                                }
 
                                break; 

                            case ResourceTypeKind.ComplexType: 
                                if (!lastPathSegment)
                                {
                                    throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_ComplexPropertyAsInnerSelectSegment(currentResourceType.FullName, pathSegment));
                                } 

                                break; 
 
                            case ResourceTypeKind.EntityType:
                                break; 

                            default:
                                Debug.Fail("Unexpected property type.");
                                break; 
                        }
                    } 
 
                    // If we already have such segment, reuse it. We ignore duplicates on the input.
                    ProjectionNode newNode = currentNode.FindNode(pathSegment); 
                    if (newNode == null)
                    {
                        // We need expanded node to already exist, otherwise the projection is invalid
                        if (!lastPathSegment) 
                        {
                            throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_ProjectedPropertyWithoutMatchingExpand(currentNode.PropertyName)); 
                        } 

                        // If it's the last segment, just create a simple projection node for it 
                        newNode = new ProjectionNode(pathSegment, property);
                        currentNode.AddNode(newNode);
                    }
 
                    currentNode = newNode as ExpandedProjectionNode;
 
                    Debug.Assert( 
                        currentNode == null || currentNode.ResourceType == property.ResourceType,
                        "If we're traversing over a nav. property it's resource type must match the resource type of the expanded segment."); 

                    // If this is the last segment in the path and it was a navigation property,
                    // mark it to include the entire subtree
                    if (lastPathSegment && currentNode != null) 
                    {
                        currentNode.ProjectionFound = true; 
                        currentNode.MarkSubtreeAsProjected(); 
                    }
                } 
            }
        }

        /// Processes the $select argument of the request. 
        private void ProcessSelect()
        { 
            // Parse the $select query option into paths 
            string select = this.service.OperationContext.Host.GetQueryStringItem(XmlConstants.HttpQueryStringSelect);
            List> selectPathsAsText; 
            if (!String.IsNullOrEmpty(select))
            {
                // Throw if $select requests have been disabled by the user
                if (!this.service.Configuration.DataServiceBehavior.AcceptProjectionRequests) 
                {
                    throw DataServiceException.CreateBadRequestError(Strings.DataServiceConfiguration_ProjectionsNotSupportedInV1Server); 
                } 

                selectPathsAsText = ReadExpandOrSelect(select, true); 
            }
            else
            {
                // No projections specified on the input 
                if (this.rootProjectionNode != null)
                { 
                    // There are expansions, but no projections 
                    // Mark all the expanded nodes in the tree to include all properties.
                    this.rootProjectionNode.MarkSubtreeAsProjected(); 
                }

                return;
            } 

            // We only allow $select on true queries (we must have IQueryable) 
            if (selectPathsAsText.Count > 0 && this.query == null) 
            {
                throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QuerySelectOptionNotApplicable); 
            }

            // We only allow $select on entity/entityset queries. Queries which return a primitive/complex value don't support $select.
            if (this.description.TargetResourceType == null || (this.description.TargetResourceType.ResourceTypeKind != ResourceTypeKind.EntityType)) 
            {
                throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QuerySelectOptionNotApplicable); 
            } 

            // $select can't be used on $links URIs as it doesn't make sense 
            if (this.description.SegmentInfos.Any(si => si.TargetKind == RequestTargetKind.Link))
            {
                throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QuerySelectOptionNotApplicable);
            } 

            // We require the request to have 2.0 version when using $select 
            this.description.RaiseMinimumVersionRequirement(2, 0); 

            // We found some projections in the query - mark it as such 
            this.GetRootProjectionNode().ProjectionsSpecified = true;

            // Apply projections to the $expand tree
            this.ApplyProjectionsToExpandTree(selectPathsAsText); 

            // Cleanup the tree 
            if (this.rootProjectionNode != null) 
            {
                // Remove all expanded nodes which are not projected 
                this.rootProjectionNode.RemoveNonProjectedNodes();

                // Now cleanup the projected tree. We already eliminated explicit duplicates during construction
                //   now we want to remove redundant properties when * was used or when the subtree was already selected. 
                this.rootProjectionNode.ApplyWildcardsAndSort();
            } 
        } 

        ///  
        /// Generate the queryResults for the request
        /// 
        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining | System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)]
        private void GenerateQueryResult() 
        {
            if (this.description.CountOption == RequestQueryCountOption.ValueOnly) 
            { 
                // $count segment will be counted last, before Expand and SDP affects the query
                MethodInfo mi = countQueryResultMethodInfo.MakeGenericMethod(this.query.ElementType); 
                this.description.RaiseMinimumVersionRequirement(2, 0);
                this.description.RaiseResponseVersion(2, 0);

                // set query result to Count 
                this.queryResults = new long[] { (long)mi.Invoke(null, new object[] { this.query }) }
                    .AsEnumerable(); 
            } 
            else if (this.rootProjectionNode != null)
            { 
                IExpandProvider expandProvider = this.service.Provider.GetService(this.service);
                if (expandProvider != null)
                {
                    // If top level result is paged, then we can not perform the operation since that would require 
                    // passing in the ordering expressions for $skiptoken generation for top level results which
                    // the IExpandProvider interface does not support and thus we would get wrong results 
                    if (this.IsStandardPaged) 
                    {
                        throw new DataServiceException(500, Strings.DataService_SDP_TopLevelPagedResultWithOldExpandProvider); 
                    }

                    // If there's projection we can't perform the operation since that would require
                    // passing the projection info into the IExpandProvider, which it doesn't support 
                    // and thus would not perform the projection.
                    if (this.rootProjectionNode.ProjectionsSpecified) 
                    { 
                        throw new DataServiceException(500, Strings.DataService_Projections_ProjectionsWithOldExpandProvider);
                    } 

                    // V1 behaviour of expand in this case, although this is semantically incorrect, since we are doing
                    // a select after orderby and skip top, which could mess up the results, assuming here that count
                    // has already been processed 
                    this.ProcessOrderBy();
                    this.ProcessSkipAndTop(); 
                    this.queryResults = expandProvider.ApplyExpansions(this.query, this.rootProjectionNode.ExpandPaths); 

                    // We need to mark the ExpandPaths as "possibly modified" and make serializer use those instead. 
                    this.rootProjectionNode.UseExpandPathsForSerialization = true;
                }
                else
                { 
                    IProjectionProvider projectionProvider = this.service.Provider.ProjectionProvider;
 
                    // We already have the parameter information 
                    // * Ordering expressions through ObtainOrderingExpressions
                    // * Skip & Top through ObtainSkipTopCounts 
                    if (projectionProvider == null)
                    {
                        Debug.Assert(!this.service.Provider.IsV1Provider, "All V1 providers should implement the IProjectionProvider interface.");
 
                        // We are to run a query against IDSQP. Since the IProjectionProvider interface is not public
                        //   the provider will have to be able to handle queries generated by our BasicExpandProvider 
                        //   so we should make only minimalistic assumptions about the provider's abilities. 
                        // In here we will assume that:
                        //   - the provider doesn't expand on demand, that is we need to generate projections for all expansions in the query 
                        //   - the provider requires correct casting to "System.Object" when we assign a value to a property of type "System.Object"
                        // A side effect of these assumptions is that if the provider just propagates the calls (with small modifications) to Linq to Objects
                        //   the query should just work (nice property, as this might be rather common).
                        projectionProvider = new BasicExpandProvider(this.service.Provider, false, true); 
                    }
 
                    this.query = projectionProvider.ApplyProjections(this.query, this.rootProjectionNode); 
                    this.queryResults = this.query;
                } 
            }
            else
            {
                // Special case where although there were expand expressions, they were ignored because they did not refer to entity sets 
                if (!String.IsNullOrEmpty(this.service.OperationContext.Host.GetQueryStringItem(XmlConstants.HttpQueryStringExpand)))
                { 
                    this.ProjectSkipTokenForNonExpand(); 
                    this.ProcessOrderBy();
                    this.ProcessSkipAndTop(); 
                }

                this.queryResults = this.query;
            } 

            Debug.Assert(this.queryResults != null, "this.queryResults != null"); 
        } 

        /// Processes the $filter argument of the request. 
        private void ProcessFilter()
        {
            string filter = this.service.OperationContext.Host.GetQueryStringItem(XmlConstants.HttpQueryStringFilter);
            if (!String.IsNullOrEmpty(filter)) 
            {
                this.CheckFilterQueryApplicable(); 
                this.query = RequestQueryParser.Where(this.service, this.description.LastSegmentInfo.TargetContainer, this.description.TargetResourceType, this.query, filter); 
            }
        } 

        /// Processes the $skiptoken argument of the request.
        private void ProcessSkipToken()
        { 
            // Obtain skip token from query parameters.
            String skipToken = this.service.OperationContext.Host.GetQueryStringItem(XmlConstants.HttpQueryStringSkipToken); 
 
            if (this.pagingApplicable)
            { 
                if (this.IsCustomPaged)
                {
                    this.ApplyCustomPaging(skipToken);
                } 
                else
                { 
                    this.ApplyStandardPaging(skipToken); 
                }
            } 
            else
            if (!String.IsNullOrEmpty(skipToken))
            {
                throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_SkipTokenNotAllowed); 
            }
        } 
 
        /// Applies standard paging to the query.
        /// Skip token obtained from query parameters. 
        private void ApplyStandardPaging(string skipToken)
        {
            Debug.Assert(!this.IsCustomPaged, "Custom paging should be disabled for this function to be called.");
            if (!String.IsNullOrEmpty(skipToken)) 
            {
                // Parse the skipToken to obtain each positional value 
                KeyInstance k = null; 
                WebUtil.CheckSyntaxValid(KeyInstance.TryParseNullableTokens(skipToken, out k));
 
                // Ordering constraint in the presence of skipToken will have the following settings:
                // * First there will be the provided constraints based on $orderby
                // * Followed by all the key columns in the resource type
 
                // Validate that the skipToken had as many positional values as the number of ordering constraints
                if (this.topLevelOrderingInfo.OrderingExpressions.Count != k.PositionalValues.Count) 
                { 
                    throw DataServiceException.CreateBadRequestError(Strings.DataService_SDP_SkipTokenNotMatchingOrdering(k.PositionalValues.Count, skipToken, this.topLevelOrderingInfo.OrderingExpressions.Count));
                } 

                // Build the Where clause with the provided skip token
                this.query = RequestUriProcessor.InvokeWhereForType(
                                this.query, 
                                this.orderingParser.BuildSkipTokenFilter(this.topLevelOrderingInfo, k));
 
                // $skiptoken is expected to be only sent by 2.0 & above clients 
                this.description.RaiseMinimumVersionRequirement(2, 0);
                this.description.RaiseResponseVersion(2, 0); 
            }
        }

        /// Applies custom paging to the query. 
        /// Skip token obtained from query parameters.
        private void ApplyCustomPaging(string skipToken) 
        { 
            Debug.Assert(this.IsCustomPaged, "Custom paging should be enabled for this function to be called.");
            if (!String.IsNullOrEmpty(skipToken)) 
            {
                // Parse the skipToken to obtain each positional value
                KeyInstance k = null;
                WebUtil.CheckSyntaxValid(KeyInstance.TryParseNullableTokens(skipToken, out k)); 

                ParameterExpression p = Expression.Parameter(this.description.LastSegmentInfo.TargetResourceType.InstanceType, "it"); 
 
                RequestQueryParser.ExpressionParser skipTokenParser = new RequestQueryParser.ExpressionParser(
                                            this.service, 
                                            this.description.LastSegmentInfo.TargetContainer,
                                            this.description.LastSegmentInfo.TargetResourceType,
                                            p,
                                            ""); 

                object[] convertedValues = new object[k.PositionalValues.Count]; 
                int i = 0; 
                foreach (var value in k.PositionalValues)
                { 
                    convertedValues[i++] = skipTokenParser.ParseSkipTokenLiteral((string)value);
                }

                this.CheckAndApplyCustomPaging(convertedValues); 

                // $skiptoken is expected to be only sent by 2.0 & above clients 
                this.description.RaiseMinimumVersionRequirement(2, 0); 
            }
            else 
            {
                this.CheckAndApplyCustomPaging(null);
            }
        } 

        /// Processes the $orderby argument of the request. 
        private void ProcessOrderBy() 
        {
            Debug.Assert(this.topLevelOrderingInfo != null, "Must have valid ordering information in ProcessOrderBy"); 
            if (this.topLevelOrderingInfo.OrderingExpressions.Count > 0)
            {
                this.query = RequestQueryParser.OrderBy(this.service, this.query, this.topLevelOrderingInfo);
                this.orderApplied = true; 
            }
        } 
 
        /// Processes the $inlinecount argument of the request.
        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining | System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)] 
        private void ProcessCount()
        {
            string count = this.service.OperationContext.Host.GetQueryStringItem(XmlConstants.HttpQueryStringInlineCount);
 
            if (!String.IsNullOrEmpty(count))
            { 
                // Throw if $inlinecount requests have been disabled by the user 
                if (!this.service.Configuration.DataServiceBehavior.AcceptCountRequests)
                { 
                    throw DataServiceException.CreateBadRequestError(Strings.DataServiceConfiguration_CountNotSupportedInV1Server);
                }

                count = count.TrimStart(); 

                // none 
                if (count.Equals(XmlConstants.UriRowCountOffOption)) 
                {
                    return; 
                }

                // only get
                if (this.service.OperationContext.Host.AstoriaHttpVerb != AstoriaVerbs.GET) 
                {
                    throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_RequestVerbCannotCountError); 
                } 

                // if already counting value only, then fail 
                if (this.description.CountOption == RequestQueryCountOption.ValueOnly)
                {
                    throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_InlineCountWithValueCount);
                } 

                // Only applied to a set 
                this.CheckSetQueryApplicable(); 

                MethodInfo mi = countQueryResultMethodInfo.MakeGenericMethod(this.query.ElementType); 

                if (count == XmlConstants.UriRowCountAllOption)
                {
                    this.description.CountValue = (long)mi.Invoke(null, new object[] { this.query }); 
                    this.description.CountOption = RequestQueryCountOption.Inline;
                    this.description.RaiseMinimumVersionRequirement(2, 0); 
                    this.description.RaiseResponseVersion(2, 0); 
                }
                else 
                {
                    throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_InvalidCountOptionError);
                }
            } 
        }
 
        ///  
        /// Builds the collection of ordering expressions including implicit ordering if paging is required at top level
        ///  
        private void ObtainOrderingExpressions()
        {
            const String Comma = ",";
            const char Space = ' '; 
            const String AscendingOrderIdentifier = "asc";
 
            StringBuilder orderBy = new StringBuilder(this.service.OperationContext.Host.GetQueryStringItem(XmlConstants.HttpQueryStringOrderBy)); 
            if (orderBy.Length > 0)
            { 
                this.CheckSetQueryApplicable();
            }

            Debug.Assert(this.topLevelOrderingInfo == null, "Must only be called once per query"); 

            ResourceType rt = this.description.TargetResourceType; 
            Debug.Assert(rt != null || this.setQueryApplicable == false, "Resource type must be known for Ordering to be applied."); 

            this.topLevelOrderingInfo = new OrderingInfo(this.IsStandardPaged); 

            // We need to generate ordering expression, if either the result is paged, or we have
            // skip or top count request because in that case, the skip or top has to happen in
            // the expand provider 
            if (this.IsStandardPaged || this.topCount.HasValue || this.skipCount.HasValue)
            { 
                // Additional ordering expressions in case paging is supported 
                String separator = orderBy.Length > 0 ? Comma : String.Empty;
                foreach (var keyProp in rt.KeyProperties) 
                {
                    orderBy.Append(separator).Append(keyProp.Name).Append(Space).Append(AscendingOrderIdentifier);
                    separator = Comma;
                } 

                Debug.Assert(this.query.ElementType == rt.InstanceType, "Resource type should match query element type when in this function"); 
            } 

            String orderByText = orderBy.ToString(); 

            if (!String.IsNullOrEmpty(orderByText))
            {
                ParameterExpression elementParameter = Expression.Parameter(rt.InstanceType, "element"); 

                this.orderingParser = new RequestQueryParser.ExpressionParser( 
                                            this.service, 
                                            this.description.LastSegmentInfo.TargetContainer,
                                            rt, 
                                            elementParameter,
                                            orderByText);

                IEnumerable ordering = this.orderingParser.ParseOrdering(); 

                foreach (OrderingExpression o in ordering) 
                { 
                    this.topLevelOrderingInfo.Add(new OrderingExpression(Expression.Lambda(o.Expression, elementParameter), o.IsAscending));
                } 

                if (this.IsStandardPaged)
                {
                    this.description.SkipTokenExpressionCount = this.topLevelOrderingInfo.OrderingExpressions.Count; 
                    this.description.SkipTokenProperties = NeedSkipTokenVisitor.CollectSkipTokenProperties(this.topLevelOrderingInfo, rt);
                } 
            } 
        }
 
        /// 
        /// Processes query arguments and returns a request description for
        /// the resulting query.
        ///  
        /// A modified  that includes query information.
        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining | System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)] 
        private RequestDescription ProcessQuery() 
        {
            // Obtains the values of $skip and $top arguments 
            this.ObtainSkipTopCounts();

            // Obtain ordering information for the current request
            this.ObtainOrderingExpressions(); 

            // NOTE: The order set by ProcessOrderBy may be reset by other operations other than skip/top, 
            // so filtering needs to occur first. 
            this.ProcessFilter();
 
            // $inlinecount ignores SDP, Skip and Top
            this.ProcessCount();

            // $skiptoken is just like a filter, so it should be immediately after $filter 
            this.ProcessSkipToken();
 
            if (String.IsNullOrEmpty(this.service.OperationContext.Host.GetQueryStringItem(XmlConstants.HttpQueryStringExpand)) 
                && String.IsNullOrEmpty(this.service.OperationContext.Host.GetQueryStringItem(XmlConstants.HttpQueryStringSelect)))
            { 
                this.ProjectSkipTokenForNonExpand();
                this.ProcessOrderBy();
                this.ProcessSkipAndTop();
            } 
            else if (this.description.CountOption == RequestQueryCountOption.ValueOnly)
            { 
                // We need to take $top and $skip into account when executing $count requests. 
                // The issue is if there is a projection, we push the $skip and $top into the projection node
                // and hence we miss it when $count with $select is specified. 
                this.ProcessOrderBy();
                this.ProcessSkipAndTop();
            }
 
            // NOTE: expand goes last, as it may be reset by other operations.
            this.ProcessExpand(); 
 
            this.ProcessSelect();
 
            this.GenerateQueryResult();
#if DEBUG
            // We analyze the query here to detect if we have incorrectly inserted
            // calls to OTM for non-open types 
            if (this.service.Provider.AreAllResourceTypesNonOpen)
            { 
                // Verify that there is no call to OTMs here. 
                if (!(this.queryResults is BaseServiceProvider.QueryableOverEnumerable))
                { 
                    OpenTypeMethodCallDetector.CheckForOpenTypeMethodCalls((this.queryResults as IQueryable ?? this.query).Expression);
                }
            }
 
            // This is a hook for exposing the query out to debuggers and tests.
            Type serviceType = this.service.Instance.GetType(); 
            while (serviceType.BaseType != null) 
            {
                try 
                {
                    FieldInfo delegateField = serviceType.GetField("requestQueryableConstructed", BindingFlags.NonPublic | BindingFlags.Instance);
                    if (delegateField != null)
                    { 
                        // Batch services do not have this delegate
                        Action action = delegateField.GetValue(this.service.Instance) as Action; 
                        if (action != null) 
                        {
                            // If the actual results are IQueryable, then report that (since it's the actual query we're going to execute) 
                            if (this.queryResults is IQueryable)
                            {
                                action.Invoke((IQueryable)this.queryResults);
                            } 
                            else
                            { 
                                action.Invoke(this.query); 
                            }
                        } 
                        break;
                    }

                    serviceType = serviceType.BaseType; 
                }
                catch (FieldAccessException) 
                { 
                    // in medium trust settings, we can't get reflection permission
                    // just break out of the loop then 
                    break;
                }
            }
#endif 

            Debug.Assert(this.queryResults != null, "this.queryResults != null -- otherwise ProcessExpand didn't set it"); 
 
            return new RequestDescription(this.description, this.queryResults, this.rootProjectionNode);
        } 

        /// 
        /// In case $expand is not provided while the results are still paged, we need to create a wrapper
        /// for the object in order to project the skip tokens corresponding to the result sequence 
        /// 
        private void ProjectSkipTokenForNonExpand() 
        { 
            if (this.IsStandardPaged && this.description.SkipTokenProperties == null)
            { 
                Type queryElementType = this.query.ElementType;

                ParameterExpression expandParameter = Expression.Parameter(queryElementType, "p");
 
                StringBuilder skipTokenDescription = new StringBuilder();
 
                Type skipTokenWrapperType = this.GetSkipTokenWrapperTypeAndDescription(skipTokenDescription); 

                MemberBinding[] skipTokenBindings = this.GetSkipTokenBindings(skipTokenWrapperType, skipTokenDescription.ToString(), expandParameter); 

                Type resultWrapperType = WebUtil.GetWrapperType(new Type[] { queryElementType, skipTokenWrapperType }, null);

                MemberBinding[] resultWrapperBindings = new MemberBinding[3]; 
                resultWrapperBindings[0] = Expression.Bind(resultWrapperType.GetProperty("ExpandedElement"), expandParameter);
                resultWrapperBindings[1] = Expression.Bind(resultWrapperType.GetProperty("Description"), Expression.Constant(XmlConstants.HttpQueryStringSkipToken)); 
                resultWrapperBindings[2] = Expression.Bind( 
                                                resultWrapperType.GetProperty("ProjectedProperty0"),
                                                Expression.MemberInit(Expression.New(skipTokenWrapperType), skipTokenBindings)); 

                Expression resultBody = Expression.MemberInit(Expression.New(resultWrapperType), resultWrapperBindings);

                this.query = RequestUriProcessor.InvokeSelectForTypes(this.query, resultWrapperType, Expression.Lambda(resultBody, expandParameter)); 

                // Updates the ordering expressions with the ones that take the result wrapper type as parameter 
                // and dereference the ExpandedElement property 
                this.UpdateOrderingInfoWithSkipTokenWrapper(resultWrapperType);
            } 
        }

        /// 
        /// Obtains the wrapper type for the $skiptoken along with description of properties in the wrapper 
        /// 
        /// Description for the skip token properties 
        /// Type of $skiptoken wrapper 
        private Type GetSkipTokenWrapperTypeAndDescription(StringBuilder skipTokenDescription)
        { 
            const String Comma = ",";

            Type[] skipTokenTypes = new Type[this.topLevelOrderingInfo.OrderingExpressions.Count + 1];
 
            skipTokenTypes[0] = this.query.ElementType;
 
            int i = 0; 
            String separator = String.Empty;
            foreach (var ordering in this.topLevelOrderingInfo.OrderingExpressions) 
            {
                skipTokenTypes[i + 1] = ((LambdaExpression)ordering.Expression).Body.Type;
                skipTokenDescription.Append(separator).Append(XmlConstants.SkipTokenPropertyPrefix + i.ToString(System.Globalization.CultureInfo.InvariantCulture));
                separator = Comma; 
                i++;
            } 
 
            return WebUtil.GetWrapperType(skipTokenTypes, Strings.BasicExpandProvider_SDP_UnsupportedOrderingExpressionBreadth);
        } 

        /// 
        /// Given the wrapper type and description, returns bindings for the wrapper type for skip token
        ///  
        /// Wrapper type
        /// Description 
        /// Top level parameter type 
        /// Array of bindings for skip token
        private MemberBinding[] GetSkipTokenBindings(Type skipTokenWrapperType, String skipTokenDescription, ParameterExpression expandParameter) 
        {
            MemberBinding[] skipTokenBindings = new MemberBinding[this.topLevelOrderingInfo.OrderingExpressions.Count + 2];
            skipTokenBindings[0] = Expression.Bind(skipTokenWrapperType.GetProperty("ExpandedElement"), expandParameter);
            skipTokenBindings[1] = Expression.Bind(skipTokenWrapperType.GetProperty("Description"), Expression.Constant(skipTokenDescription.ToString())); 

            int i = 0; 
            foreach (var ordering in this.topLevelOrderingInfo.OrderingExpressions) 
            {
                LambdaExpression sourceLambda = (LambdaExpression)ordering.Expression; 
                Expression source = ParameterReplacerVisitor.Replace(sourceLambda.Body, sourceLambda.Parameters[0], expandParameter);
                MemberInfo member = skipTokenWrapperType.GetProperty("ProjectedProperty" + i.ToString(System.Globalization.CultureInfo.InvariantCulture));
                skipTokenBindings[i + 2] = Expression.Bind(member, source);
                i++; 
            }
 
            return skipTokenBindings; 
        }
 
        /// 
        /// Updates the topLevelOrderingInfo member with the new collection of expressions that
        /// dereference the ExpandedElement property on the top level wrapper object
        ///  
        /// Type of top level wrapper object
        private void UpdateOrderingInfoWithSkipTokenWrapper(Type resultWrapperType) 
        { 
            OrderingInfo newOrderingInfo = new OrderingInfo(true);
 
            ParameterExpression wrapperParameter = Expression.Parameter(resultWrapperType, "w");

            foreach (var ordering in this.topLevelOrderingInfo.OrderingExpressions)
            { 
                LambdaExpression oldExpression = (LambdaExpression)ordering.Expression;
                Expression newOrdering = ParameterReplacerVisitor.Replace( 
                                            oldExpression.Body, 
                                            oldExpression.Parameters[0],
                                            Expression.MakeMemberAccess(wrapperParameter, resultWrapperType.GetProperty("ExpandedElement"))); 

                newOrderingInfo.Add(new OrderingExpression(Expression.Lambda(newOrdering, wrapperParameter), ordering.IsAscending));
            }
 
            this.topLevelOrderingInfo = newOrderingInfo;
        } 
 
        /// Processes the $skip and/or $top argument of the request by composing query with Skip and/or Take methods.
        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining | System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)] 
        private void ProcessSkipAndTop()
        {
            Debug.Assert(this.query != null, "this.query != null");
            if (this.skipCount.HasValue) 
            {
                Debug.Assert(this.orderApplied, "Ordering must have already been applied."); 
                this.query = (IQueryable)RequestQueryProcessor 
                                            .InvokeSkipMethodInfo
                                            .MakeGenericMethod(this.query.ElementType) 
                                            .Invoke(null, new object[] { this.query, this.skipCount.Value });
                Debug.Assert(this.query != null, "this.query != null");
            }
 
            if (this.topCount.HasValue)
            { 
                Debug.Assert(this.orderApplied, "Ordering must have already been applied."); 
                this.query = (IQueryable)RequestQueryProcessor
                                            .InvokeTakeMethodInfo 
                                            .MakeGenericMethod(this.query.ElementType)
                                            .Invoke(null, new object[] { this.query, this.topCount.Value });
                Debug.Assert(this.query != null, "this.query != null");
            } 
        }
 
        ///  
        /// Finds out the appropriate value for skip and top parameters for the current request
        ///  
        private void ObtainSkipTopCounts()
        {
            int count;
 
            if (this.ReadSkipOrTopArgument(XmlConstants.HttpQueryStringSkip, out count))
            { 
                this.skipCount = count; 
            }
 
            int pageSize = 0;
            if (this.IsStandardPaged)
            {
                pageSize = this.description.LastSegmentInfo.TargetContainer.PageSize; 
            }
 
            if (this.ReadSkipOrTopArgument(XmlConstants.HttpQueryStringTop, out count)) 
            {
                this.topCount = count; 
                if (this.IsStandardPaged && pageSize < this.topCount.Value)
                {
                    // If $top is greater than or equal to page size, we will need a $skiptoken and
                    // thus our response will be 2.0 
                    this.description.RaiseResponseVersion(2, 0);
                    this.topCount = pageSize; 
                } 
            }
            else if (this.IsStandardPaged) 
            {
                // Paging forces response version of 2.0
                this.description.RaiseResponseVersion(2, 0);
                this.topCount = pageSize; 
            }
 
            if (this.topCount != null || this.skipCount != null) 
            {
                this.CheckSetQueryApplicable(); 
            }
        }

        ///  
        /// Checks whether the specified argument should be processed and what
        /// its value is. 
        ///  
        /// Name of the query item, $top or $skip.
        /// The value for the query item. 
        /// true if the argument should be processed; false otherwise.
        private bool ReadSkipOrTopArgument(string queryItem, out int count)
        {
            Debug.Assert(queryItem != null, "queryItem != null"); 

            string itemText = this.service.OperationContext.Host.GetQueryStringItem(queryItem); 
            if (String.IsNullOrEmpty(itemText)) 
            {
                count = 0; 
                return false;
            }

            if (!Int32.TryParse(itemText, NumberStyles.Integer, CultureInfo.InvariantCulture, out count)) 
            {
                throw DataServiceException.CreateSyntaxError( 
                    Strings.RequestQueryProcessor_IncorrectArgumentFormat(queryItem, itemText)); 
            }
 
            return true;
        }

        /// Checks if custom paging is already applied, if not, applies it and raises response version. 
        /// Values of skip tokens.
        private void CheckAndApplyCustomPaging(object[] skipTokenValues) 
        { 
            if (!this.appliedCustomPaging)
            { 
                this.service.PagingProvider.PagingProviderInterface.SetContinuationToken(
                        this.query,
                        this.description.LastSegmentInfo.TargetResourceType,
                        skipTokenValues); 

                this.description.RaiseResponseVersion(2, 0); 
                this.appliedCustomPaging = true; 
            }
        } 

#if DEBUG
        /// Detects calls to OpenTypeMethods members and asserts if it finds any.
        private class OpenTypeMethodCallDetector : ALinqExpressionVisitor 
        {
            /// Public interface for using this class. 
            /// Input expression. 
            public static void CheckForOpenTypeMethodCalls(Expression input)
            { 
                new OpenTypeMethodCallDetector().Visit(input);
            }

            /// Forgiving Visit method which allows unknown expression types to pass through. 
            /// Input expression.
            /// Visit expression. 
            internal override Expression Visit(Expression exp) 
            {
                try 
                {
                    return base.Visit(exp);
                }
                catch (NotSupportedException) 
                {
                    return exp; 
                } 
            }
 
            /// Method call visitor.
            /// Method call being visited.
            /// Whatever is provided on input.
            internal override Expression VisitMethodCall(MethodCallExpression m) 
            {
                if (m.Method.DeclaringType == typeof(OpenTypeMethods)) 
                { 
                    throw new InvalidOperationException("Unexpected call to OpenTypeMethods found in query when the provider did not have any OpenTypes.");
                } 

                return base.VisitMethodCall(m);
            }
 
            internal override Expression VisitBinary(BinaryExpression b)
            { 
                if (b.Method != null && b.Method.DeclaringType == typeof(OpenTypeMethods)) 
                {
                    throw new InvalidOperationException("Unexpected call to OpenTypeMethods found in query when the provider did not have any OpenTypes."); 
                }

                return base.VisitBinary(b);
            } 

            internal override Expression VisitUnary(UnaryExpression u) 
            { 
                if (u.Method != null && u.Method.DeclaringType == typeof(OpenTypeMethods))
                { 
                    throw new InvalidOperationException("Unexpected call to OpenTypeMethods found in query when the provider did not have any OpenTypes.");
                }

                return base.VisitUnary(u); 
            }
        } 
#endif 
    }
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//---------------------------------------------------------------------- 
// 
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// 
//  
//      Provides a class capable of processing query arguments.
//  
// 
// @owner  [....]
//--------------------------------------------------------------------- 

namespace System.Data.Services
{
    using System; 
    using System.Collections;
    using System.Collections.Generic; 
    using System.Data.Services.Client; 
    using System.Data.Services.Internal;
    using System.Data.Services.Parsing; 
    using System.Data.Services.Providers;
    using System.Diagnostics;
    using System.Globalization;
    using System.Linq; 
    using System.Linq.Expressions;
    using System.Reflection; 
    using System.Text; 

    /// Use this class to process a web data service request URI. 
    internal struct RequestQueryProcessor
    {
        #region Private fields.
 
        /// MethodInfo for Skip.
        private static readonly MethodInfo InvokeSkipMethodInfo = typeof(RequestQueryProcessor).GetMethod("InvokeSkip", BindingFlags.NonPublic | BindingFlags.Static); 
 
        /// MethodInfo for Take.
        private static readonly MethodInfo InvokeTakeMethodInfo = typeof(RequestQueryProcessor).GetMethod("InvokeTake", BindingFlags.NonPublic | BindingFlags.Static); 

        /// The generic method for CountQueryResult'[T]()
        private static readonly MethodInfo countQueryResultMethodInfo = typeof(RequestQueryProcessor).GetMethod("CountQueryResult", BindingFlags.NonPublic | BindingFlags.Static);
 
        /// Original description over which query composition takes place.
        private readonly RequestDescription description; 
 
        /// Whether the $filter query option can be applied to the request.
        private readonly bool filterQueryApplicable; 

        /// Service with data and configuration.
        private readonly IDataService service;
 
        /// Whether the $orderby, $skip, $take and $count options can be applied to the request.
        private readonly bool setQueryApplicable; 
 
        /// Whether the top level request is a candidate for paging.
        private readonly bool pagingApplicable; 

        /// Has custom paging already been applied?
        private bool appliedCustomPaging;
 
        /// List of paths to be expanded.
        private List expandPaths; 
 
        /// List of paths to be expanded, before resolving the identifiers
        private List> expandPathsAsText; 

        /// Root projection segment of the tree specifying projections and expansions for the query.
        private RootProjectionNode rootProjectionNode;
 
        /// Parser used for parsing ordering expressions
        private RequestQueryParser.ExpressionParser orderingParser; 
 
        /// Collection of ordering expressions for the current query
        private OrderingInfo topLevelOrderingInfo; 

        /// Whether any order has been applied.
        private bool orderApplied;
 
        /// Value of $skip argument
        private int? skipCount; 
 
        /// Value of $top argument
        private int? topCount; 

        /// Query being composed.
        private IQueryable query;
 
        /// Query results to be returned.
        private IEnumerable queryResults; 
 
        #endregion Private fields.
 
        /// Initializes a new  instance.
        /// Service with data and configuration.
        /// Description for request processed so far.
        private RequestQueryProcessor(IDataService service, RequestDescription description) 
        {
            this.service = service; 
            this.description = description; 
            this.orderApplied = false;
            this.skipCount = null; 
            this.topCount = null;
            this.query = (IQueryable)description.RequestEnumerable;

            this.filterQueryApplicable = 
                description.TargetKind == RequestTargetKind.Resource ||
                description.TargetKind == RequestTargetKind.OpenProperty || 
                description.TargetKind == RequestTargetKind.ComplexObject || 
                (description.CountOption == RequestQueryCountOption.ValueOnly);
 
            this.setQueryApplicable = (description.TargetKind == RequestTargetKind.Resource && !description.IsSingleResult) ||
                                       description.CountOption == RequestQueryCountOption.ValueOnly;

            // Server Driven Paging is not considered for the following cases: 
            // 1. Top level result is not or resource type or it is a single valued result.
            // 2. $count segment provided. 
            // 3. Non-GET requests do not honor SDP. 
            // 4. Only exception for Non-GET requests is if the request is coming from a Service
            //    operation that returns a set of result values of entity type. 
            this.pagingApplicable = (description.TargetKind == RequestTargetKind.Resource && !description.IsSingleResult) &&
                                    (description.CountOption != RequestQueryCountOption.ValueOnly) &&
                                    !description.IsRequestForEnumServiceOperation &&
                                    (service.OperationContext.Host.AstoriaHttpVerb == AstoriaVerbs.GET || description.SegmentInfos[0].TargetSource == RequestTargetSource.ServiceOperation); 

            this.appliedCustomPaging = false; 
 
            this.expandPaths = null;
            this.expandPathsAsText = null; 
            this.rootProjectionNode = null;
            this.orderingParser = null;
            this.topLevelOrderingInfo = null;
            this.queryResults = null; 
        }
 
        ///  
        /// Is the top level container for the query paged i.e. we need to use StandardPaging.
        ///  
        private bool IsStandardPaged
        {
            get
            { 
                if (this.pagingApplicable && !this.IsCustomPaged)
                { 
                    ResourceSetWrapper targetContainer = this.description.LastSegmentInfo.TargetContainer; 
                    Debug.Assert(targetContainer != null, "Must have target container for non-open properties");
                    return targetContainer.PageSize != 0; 
                }
                else
                {
                    // Target container is null for OpenProperties and we have decided not to 
                    // to enable paging for OpenType scenarios
                    return false; 
                } 
            }
        } 

        /// Do we need to use CustomPaging for this service.
        private bool IsCustomPaged
        { 
            get
            { 
                return this.service.PagingProvider.IsCustomPagedForQuery; 
            }
        } 

        /// Checks that no query arguments were sent in the request.
        /// Service to check.
        /// true if only V2 query parameters must be checked, otherwise all the query parameters will be checked. 
        /// 
        /// Regular processing checks argument applicability, but for 
        /// service operations that return an IEnumerable this is part 
        /// of the contract on service operations, rather than a structural
        /// check on the request. 
        /// 
        internal static void CheckEmptyQueryArguments(IDataService service, bool checkForOnlyV2QueryParameters)
        {
            Debug.Assert(service != null, "service != null"); 

            DataServiceHostWrapper host = service.OperationContext.Host; 
            if ((!checkForOnlyV2QueryParameters && 
                 (!String.IsNullOrEmpty(host.GetQueryStringItem(XmlConstants.HttpQueryStringExpand)) ||
                  !String.IsNullOrEmpty(host.GetQueryStringItem(XmlConstants.HttpQueryStringFilter)) || 
                  !String.IsNullOrEmpty(host.GetQueryStringItem(XmlConstants.HttpQueryStringOrderBy)) ||
                  !String.IsNullOrEmpty(host.GetQueryStringItem(XmlConstants.HttpQueryStringSkip)) ||
                  !String.IsNullOrEmpty(host.GetQueryStringItem(XmlConstants.HttpQueryStringTop)))) ||
                !String.IsNullOrEmpty(host.GetQueryStringItem(XmlConstants.HttpQueryStringInlineCount)) || 
                !String.IsNullOrEmpty(host.GetQueryStringItem(XmlConstants.HttpQueryStringSelect)) ||
                !String.IsNullOrEmpty(host.GetQueryStringItem(XmlConstants.HttpQueryStringSkipToken))) 
            { 
                // 400: Bad Request
                throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QueryNoOptionsApplicable); 
            }
        }

        /// Checks that no query arguments were sent in the request. 
        /// Service to check.
        ///  
        /// Regular processing checks argument applicability, but for 
        /// service operations that return an IEnumerable this is part
        /// of the contract on service operations, rather than a structural 
        /// check on the request.
        /// 
        internal static void CheckV2EmptyQueryArguments(IDataService service)
        { 
            Debug.Assert(service != null, "service != null");
            CheckEmptyQueryArguments(service, service.Provider.IsV1Provider); 
        } 

        ///  
        /// Composes a property navigation with the appropriate filter lamba, as appropriate.
        /// 
        /// Member access expression to compose.
        /// Lambda expression used for the filter. 
        /// Whether null propagation is required on the .
        /// The composed expression. 
        internal static Expression ComposePropertyNavigation( 
            Expression expression,
            LambdaExpression filterLambda, 
            bool propagateNull)
        {
            Debug.Assert(expression != null, "expression != null");
            Debug.Assert(filterLambda != null, "filterLambda != null"); 

            Expression fixedFilter = ParameterReplacerVisitor.Replace( 
                    filterLambda.Body, 
                    filterLambda.Parameters[0],
                    expression); 

            Expression test;
            if (propagateNull)
            { 
                Expression nullConstant = WebUtil.NullLiteral;
                test = Expression.AndAlso(Expression.NotEqual(expression, nullConstant), fixedFilter); 
            } 
            else
            { 
                test = fixedFilter;
            }

            Expression conditionTrue = expression; 
            Expression conditionFalse = Expression.Constant(null, conditionTrue.Type);
            return Expression.Condition(test, conditionTrue, conditionFalse); 
        } 

        ///  
        /// Processes query arguments and returns a request description for
        /// the resulting query.
        /// 
        /// Service with data and configuration information. 
        /// Description for request processed so far.
        /// A new . 
        internal static RequestDescription ProcessQuery(IDataService service, RequestDescription description) 
        {
            Debug.Assert(service != null, "service != null"); 

            // In some cases, like CUD operations, we do not want to allow any query parameters to be specified.
            // But in V1, we didn't have this check hence we cannot fix this now. But we need to check only for
            // V2 query parameters and stop them 
            if ((service.OperationContext.Host.AstoriaHttpVerb != AstoriaVerbs.GET) &&
                description.SegmentInfos[0].TargetSource != RequestTargetSource.ServiceOperation) 
            { 
                RequestQueryProcessor.CheckV2EmptyQueryArguments(service);
            } 

            // When the request doesn't produce an IQueryable result,
            // we can short-circuit all further processing.
            if (!(description.RequestEnumerable is IQueryable)) 
            {
                RequestQueryProcessor.CheckEmptyQueryArguments(service, false /*checkForOnlyV2QueryParameters*/); 
                return description; 
            }
            else 
            {
                return new RequestQueryProcessor(service, description).ProcessQuery();
            }
        } 

        /// Generic method to invoke a Skip method on an IQueryable source. 
        /// Element type of the source. 
        /// Source query.
        /// Element count to skip. 
        /// A new query that skips  elements of .
        private static IQueryable InvokeSkip(IQueryable source, int count)
        {
            Debug.Assert(source != null, "source != null"); 
            IQueryable typedSource = (IQueryable)source;
            return Queryable.Skip(typedSource, count); 
        } 

        /// Generic method to invoke a Take method on an IQueryable source. 
        /// Element type of the source.
        /// Source query.
        /// Element count to take.
        /// A new query that takes  elements of . 
        private static IQueryable InvokeTake(IQueryable source, int count)
        { 
            Debug.Assert(source != null, "source != null"); 
            IQueryable typedSource = (IQueryable)source;
            return Queryable.Take(typedSource, count); 
        }

        /// Count the query result before $top and $skip are applied
        /// Element type of the source 
        /// Source query
        /// The count from the provider 
        private static long CountQueryResult(IQueryable query) 
        {
            return Queryable.LongCount(query); 
        }

        /// Reads an $expand or $select clause.
        /// Value to read. 
        /// True if we're parsing $select clause False for $expand
        /// A list of paths, each of which is a list of identifiers. 
        /// Same method is used for both $expand and $select as the syntax is almost identical. 
        /// $select allows * at the end of the path while $expand doesn't.
        private static List> ReadExpandOrSelect(string value, bool select) 
        {
            Debug.Assert(!String.IsNullOrEmpty(value), "!String.IsNullOrEmpty(value)");

            List> result = new List>(); 
            List currentPath = null;
            ExpressionLexer lexer = new ExpressionLexer(value); 
            while (lexer.CurrentToken.Id != TokenId.End) 
            {
                string identifier; 
                bool lastSegment = false;
                if (select && lexer.CurrentToken.Id == TokenId.Star)
                {
                    identifier = lexer.CurrentToken.Text; 
                    lexer.NextToken();
                    lastSegment = true; 
                } 
                else
                { 
                    identifier = lexer.ReadDottedIdentifier();
                }

                if (currentPath == null) 
                {
                    currentPath = new List(); 
                    result.Add(currentPath); 
                }
 
                currentPath.Add(identifier);

                // Check whether we're at the end, whether we're drilling in,
                // or whether we're finishing with this path. 
                TokenId tokenId = lexer.CurrentToken.Id;
                if (tokenId != TokenId.End) 
                { 
                    if (lastSegment || tokenId != TokenId.Slash)
                    { 
                        lexer.ValidateToken(TokenId.Comma);
                        currentPath = null;
                    }
 
                    lexer.NextToken();
                } 
            } 

            return result; 
        }

        /// Gets the root projection node or creates one if no one exists yet.
        /// The root node of the projection tree. 
        private RootProjectionNode GetRootProjectionNode()
        { 
            if (this.rootProjectionNode == null) 
            {
                // Build the root of the projection and expansion tree 
                this.rootProjectionNode = new RootProjectionNode(
                    this.description.LastSegmentInfo.TargetContainer,
                    this.topLevelOrderingInfo,
                    null, 
                    this.skipCount,
                    this.topCount, 
                    null, 
                    this.expandPaths,
                    this.description.TargetResourceType); 
            }

            return this.rootProjectionNode;
        } 

        /// Checks and resolved all textual expand paths and removes unnecessary paths. 
        private void CheckExpandPaths() 
        {
            Debug.Assert(this.expandPathsAsText != null, "this.expandPaths != null"); 
            if (this.expandPathsAsText.Count > 0 && this.query == null)
            {
                throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QueryExpandOptionNotApplicable);
            } 

            this.expandPaths = new List(this.expandPathsAsText.Count); 
 
            for (int i = this.expandPathsAsText.Count - 1; i >= 0; i--)
            { 
                ResourceType resourceType = this.description.TargetResourceType;
                ResourceSetWrapper resourceSet = this.description.LastSegmentInfo.TargetContainer;

                List path = this.expandPathsAsText[i]; 
                ExpandSegmentCollection segments = new ExpandSegmentCollection(path.Count);
                bool ignorePath = false; 
                for (int j = 0; j < path.Count; j++) 
                {
                    string pathSegment = path[j]; 
                    ResourceProperty property = resourceType.TryResolvePropertyName(pathSegment);

                    if (property == null)
                    { 
                        if (resourceType.IsOpenType)
                        { 
                            // Open navigation properties are not supported on OpenTypes. 
                            throw DataServiceException.CreateBadRequestError(Strings.OpenNavigationPropertiesNotSupportedOnOpenTypes(pathSegment));
                        } 

                        throw DataServiceException.CreateSyntaxError(Strings.RequestUriProcessor_PropertyNotFound(resourceType.FullName, pathSegment));
                    }
 
                    if (property.TypeKind == ResourceTypeKind.EntityType)
                    { 
                        Expression filter = null; 
                        resourceSet = this.service.Provider.GetContainer(resourceSet, resourceType, property);
                        if (resourceSet == null) 
                        {
                            throw DataServiceException.CreateBadRequestError(Strings.BadRequest_InvalidPropertyNameSpecified(property.Name, resourceType.FullName));
                        }
 
                        resourceType = resourceSet.ResourceType;
 
                        bool singleResult = property.Kind == ResourcePropertyKind.ResourceReference; 
                        DataServiceConfiguration.CheckResourceRightsForRead(resourceSet, singleResult);
                        filter = DataServiceConfiguration.ComposeQueryInterceptors(this.service, resourceSet); 

                        if (resourceSet.PageSize != 0 && !singleResult && !this.IsCustomPaged)
                        {
                            OrderingInfo internalOrderingInfo = new OrderingInfo(true); 
                            ParameterExpression p = Expression.Parameter(resourceType.InstanceType, "p");
                            foreach (var keyProp in resourceType.KeyProperties) 
                            { 
                                Expression e;
                                if (keyProp.CanReflectOnInstanceTypeProperty) 
                                {
                                    e = Expression.Property(p, resourceType.GetPropertyInfo(keyProp));
                                }
                                else 
                                {
                                    // object LateBoundMethods.GetValue(object, ResourceProperty) 
                                    e = Expression.Call( 
                                                null, /*instance*/
                                                DataServiceProviderMethods.GetValueMethodInfo, 
                                                p,
                                                Expression.Constant(keyProp));
                                    e = Expression.Convert(e, keyProp.Type);
                                } 

                                internalOrderingInfo.Add(new OrderingExpression(Expression.Lambda(e, p), true)); 
                            } 

                            segments.Add(new ExpandSegment(property.Name, filter, resourceSet.PageSize, resourceSet, property, internalOrderingInfo)); 

                            // Paged expanded results force the response version to be 2.0
                            this.description.RaiseResponseVersion(2, 0);
                        } 
                        else
                        { 
                            // Expansion of collection could result in custom paging provider giving next link, so we need to set the null continuation token. 
                            if (!singleResult && this.IsCustomPaged)
                            { 
                                this.CheckAndApplyCustomPaging(null);
                            }

                            segments.Add(new ExpandSegment(property.Name, filter, this.service.Configuration.MaxResultsPerCollection, resourceSet, property, null)); 
                        }
 
                        // We need to explicitly update the feature version here since we may not bump response version 
                        this.description.UpdateAndCheckEpmFeatureVersion(resourceSet, this.service);
 
                        // The expanded resource type may have friendly feeds. Update version of the response accordingly
                        //
                        // Note the response DSV is payload specific and since for GET we won't know if the instances to be
                        // serialized will contain any properties with FF KeepInContent=false until serialization time which 
                        // happens after the headers are written, the best we can do is to determin this at the set level.
                        this.description.UpdateEpmResponseVersion(this.service.OperationContext.Host.RequestAccept, resourceSet, this.service.Provider); 
 
                        ignorePath = false;
                    } 
                    else
                    {
                        ignorePath = true;
                    } 
                }
 
                // Even though the path might be correct, we might need to 
                // ignore because it's a primitive.
                if (ignorePath) 
                {
                    this.expandPathsAsText.RemoveAt(i);
                }
                else 
                {
                    this.expandPaths.Add(segments); 
 
                    // And now build the projection and expansion tree nodes for this expand path as well
                    ExpandedProjectionNode currentNode = this.GetRootProjectionNode(); 
                    for (int j = 0; j < segments.Count; j++)
                    {
                        ExpandSegment segment = segments[j];
                        ExpandedProjectionNode childNode = (ExpandedProjectionNode)currentNode.FindNode(segment.Name); 
                        if (childNode == null)
                        { 
                            childNode = new ExpandedProjectionNode( 
                                segment.Name,
                                segment.ExpandedProperty, 
                                segment.Container,
                                segment.OrderingInfo,
                                segment.Filter,
                                null, 
                                segment.Container.PageSize != 0 ? (int?)segment.Container.PageSize : null,
                                segment.MaxResultsExpected != Int32.MaxValue ? (int?)segment.MaxResultsExpected : null); 
                            currentNode.AddNode(childNode); 
                        }
 
                        currentNode = childNode;
                    }

                    this.GetRootProjectionNode().ExpansionsSpecified = true; 
                }
            } 
        } 

        /// Checks that the query option is applicable to this request. 
        private void CheckFilterQueryApplicable()
        {
            if (!this.filterQueryApplicable)
            { 
                throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QueryFilterOptionNotApplicable);
            } 
        } 

        /// Checks that set query options are applicable to this request. 
        private void CheckSetQueryApplicable()
        {
            if (!this.setQueryApplicable)
            { 
                throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QuerySetOptionsNotApplicable);
            } 
        } 

        /// Processes the $expand argument of the request. 
        private void ProcessExpand()
        {
            string expand = this.service.OperationContext.Host.GetQueryStringItem(XmlConstants.HttpQueryStringExpand);
            if (!String.IsNullOrEmpty(expand)) 
            {
                this.expandPathsAsText = ReadExpandOrSelect(expand, false); 
            } 
            else
            { 
                this.expandPathsAsText = new List>();
            }

            this.CheckExpandPaths(); 
            this.service.InternalApplyingExpansions(this.query, this.expandPaths);
        } 
 
        /// Builds the tree of  to represent the $select query option.
        /// The value of the $select query option parsed into a list of lists. 
        /// This method assumes that $expand was already processed. And we have the tree
        /// of  objects for the $expand query option already built.
        private void ApplyProjectionsToExpandTree(List> selectPathsAsText)
        { 
            for (int i = selectPathsAsText.Count - 1; i >= 0; i--)
            { 
                List path = selectPathsAsText[i]; 
                ExpandedProjectionNode currentNode = this.GetRootProjectionNode();
                Debug.Assert(currentNode.ResourceType == this.description.TargetResourceType, "The resource type of the root doesn't match the target type of the query."); 

                for (int j = 0; j < path.Count; j++)
                {
                    Debug.Assert(currentNode != null, "Can't apply projections to non-expanded nodes."); 
                    string pathSegment = path[j];
                    bool lastPathSegment = (j == path.Count - 1); 
 
                    currentNode.ProjectionFound = true;
 
                    // '*' is special, it means "Project all immediate properties on this level."
                    if (pathSegment == "*")
                    {
                        Debug.Assert(lastPathSegment, "The * select segment must be the last one. This should have been checked already by the ReadExpandOrSelect method."); 
                        currentNode.ProjectAllImmediateProperties = true;
                        break; 
                    } 

                    ResourceType currentResourceType = currentNode.ResourceType; 
                    ResourceProperty property = currentResourceType.TryResolvePropertyName(pathSegment);
                    if (property == null)
                    {
                        if (!currentResourceType.IsOpenType) 
                        {
                            throw DataServiceException.CreateSyntaxError(Strings.RequestUriProcessor_PropertyNotFound(currentResourceType.FullName, pathSegment)); 
                        } 

                        if (!lastPathSegment) 
                        {
                            // Open navigation properties are not supported on OpenTypes.
                            throw DataServiceException.CreateBadRequestError(Strings.OpenNavigationPropertiesNotSupportedOnOpenTypes(pathSegment));
                        } 
                    }
                    else 
                    { 
                        switch (property.TypeKind)
                        { 
                            case ResourceTypeKind.Primitive:
                                if (!lastPathSegment)
                                {
                                    throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_PrimitivePropertyUsedAsNavigationProperty(currentResourceType.FullName, pathSegment)); 
                                }
 
                                break; 

                            case ResourceTypeKind.ComplexType: 
                                if (!lastPathSegment)
                                {
                                    throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_ComplexPropertyAsInnerSelectSegment(currentResourceType.FullName, pathSegment));
                                } 

                                break; 
 
                            case ResourceTypeKind.EntityType:
                                break; 

                            default:
                                Debug.Fail("Unexpected property type.");
                                break; 
                        }
                    } 
 
                    // If we already have such segment, reuse it. We ignore duplicates on the input.
                    ProjectionNode newNode = currentNode.FindNode(pathSegment); 
                    if (newNode == null)
                    {
                        // We need expanded node to already exist, otherwise the projection is invalid
                        if (!lastPathSegment) 
                        {
                            throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_ProjectedPropertyWithoutMatchingExpand(currentNode.PropertyName)); 
                        } 

                        // If it's the last segment, just create a simple projection node for it 
                        newNode = new ProjectionNode(pathSegment, property);
                        currentNode.AddNode(newNode);
                    }
 
                    currentNode = newNode as ExpandedProjectionNode;
 
                    Debug.Assert( 
                        currentNode == null || currentNode.ResourceType == property.ResourceType,
                        "If we're traversing over a nav. property it's resource type must match the resource type of the expanded segment."); 

                    // If this is the last segment in the path and it was a navigation property,
                    // mark it to include the entire subtree
                    if (lastPathSegment && currentNode != null) 
                    {
                        currentNode.ProjectionFound = true; 
                        currentNode.MarkSubtreeAsProjected(); 
                    }
                } 
            }
        }

        /// Processes the $select argument of the request. 
        private void ProcessSelect()
        { 
            // Parse the $select query option into paths 
            string select = this.service.OperationContext.Host.GetQueryStringItem(XmlConstants.HttpQueryStringSelect);
            List> selectPathsAsText; 
            if (!String.IsNullOrEmpty(select))
            {
                // Throw if $select requests have been disabled by the user
                if (!this.service.Configuration.DataServiceBehavior.AcceptProjectionRequests) 
                {
                    throw DataServiceException.CreateBadRequestError(Strings.DataServiceConfiguration_ProjectionsNotSupportedInV1Server); 
                } 

                selectPathsAsText = ReadExpandOrSelect(select, true); 
            }
            else
            {
                // No projections specified on the input 
                if (this.rootProjectionNode != null)
                { 
                    // There are expansions, but no projections 
                    // Mark all the expanded nodes in the tree to include all properties.
                    this.rootProjectionNode.MarkSubtreeAsProjected(); 
                }

                return;
            } 

            // We only allow $select on true queries (we must have IQueryable) 
            if (selectPathsAsText.Count > 0 && this.query == null) 
            {
                throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QuerySelectOptionNotApplicable); 
            }

            // We only allow $select on entity/entityset queries. Queries which return a primitive/complex value don't support $select.
            if (this.description.TargetResourceType == null || (this.description.TargetResourceType.ResourceTypeKind != ResourceTypeKind.EntityType)) 
            {
                throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QuerySelectOptionNotApplicable); 
            } 

            // $select can't be used on $links URIs as it doesn't make sense 
            if (this.description.SegmentInfos.Any(si => si.TargetKind == RequestTargetKind.Link))
            {
                throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QuerySelectOptionNotApplicable);
            } 

            // We require the request to have 2.0 version when using $select 
            this.description.RaiseMinimumVersionRequirement(2, 0); 

            // We found some projections in the query - mark it as such 
            this.GetRootProjectionNode().ProjectionsSpecified = true;

            // Apply projections to the $expand tree
            this.ApplyProjectionsToExpandTree(selectPathsAsText); 

            // Cleanup the tree 
            if (this.rootProjectionNode != null) 
            {
                // Remove all expanded nodes which are not projected 
                this.rootProjectionNode.RemoveNonProjectedNodes();

                // Now cleanup the projected tree. We already eliminated explicit duplicates during construction
                //   now we want to remove redundant properties when * was used or when the subtree was already selected. 
                this.rootProjectionNode.ApplyWildcardsAndSort();
            } 
        } 

        ///  
        /// Generate the queryResults for the request
        /// 
        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining | System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)]
        private void GenerateQueryResult() 
        {
            if (this.description.CountOption == RequestQueryCountOption.ValueOnly) 
            { 
                // $count segment will be counted last, before Expand and SDP affects the query
                MethodInfo mi = countQueryResultMethodInfo.MakeGenericMethod(this.query.ElementType); 
                this.description.RaiseMinimumVersionRequirement(2, 0);
                this.description.RaiseResponseVersion(2, 0);

                // set query result to Count 
                this.queryResults = new long[] { (long)mi.Invoke(null, new object[] { this.query }) }
                    .AsEnumerable(); 
            } 
            else if (this.rootProjectionNode != null)
            { 
                IExpandProvider expandProvider = this.service.Provider.GetService(this.service);
                if (expandProvider != null)
                {
                    // If top level result is paged, then we can not perform the operation since that would require 
                    // passing in the ordering expressions for $skiptoken generation for top level results which
                    // the IExpandProvider interface does not support and thus we would get wrong results 
                    if (this.IsStandardPaged) 
                    {
                        throw new DataServiceException(500, Strings.DataService_SDP_TopLevelPagedResultWithOldExpandProvider); 
                    }

                    // If there's projection we can't perform the operation since that would require
                    // passing the projection info into the IExpandProvider, which it doesn't support 
                    // and thus would not perform the projection.
                    if (this.rootProjectionNode.ProjectionsSpecified) 
                    { 
                        throw new DataServiceException(500, Strings.DataService_Projections_ProjectionsWithOldExpandProvider);
                    } 

                    // V1 behaviour of expand in this case, although this is semantically incorrect, since we are doing
                    // a select after orderby and skip top, which could mess up the results, assuming here that count
                    // has already been processed 
                    this.ProcessOrderBy();
                    this.ProcessSkipAndTop(); 
                    this.queryResults = expandProvider.ApplyExpansions(this.query, this.rootProjectionNode.ExpandPaths); 

                    // We need to mark the ExpandPaths as "possibly modified" and make serializer use those instead. 
                    this.rootProjectionNode.UseExpandPathsForSerialization = true;
                }
                else
                { 
                    IProjectionProvider projectionProvider = this.service.Provider.ProjectionProvider;
 
                    // We already have the parameter information 
                    // * Ordering expressions through ObtainOrderingExpressions
                    // * Skip & Top through ObtainSkipTopCounts 
                    if (projectionProvider == null)
                    {
                        Debug.Assert(!this.service.Provider.IsV1Provider, "All V1 providers should implement the IProjectionProvider interface.");
 
                        // We are to run a query against IDSQP. Since the IProjectionProvider interface is not public
                        //   the provider will have to be able to handle queries generated by our BasicExpandProvider 
                        //   so we should make only minimalistic assumptions about the provider's abilities. 
                        // In here we will assume that:
                        //   - the provider doesn't expand on demand, that is we need to generate projections for all expansions in the query 
                        //   - the provider requires correct casting to "System.Object" when we assign a value to a property of type "System.Object"
                        // A side effect of these assumptions is that if the provider just propagates the calls (with small modifications) to Linq to Objects
                        //   the query should just work (nice property, as this might be rather common).
                        projectionProvider = new BasicExpandProvider(this.service.Provider, false, true); 
                    }
 
                    this.query = projectionProvider.ApplyProjections(this.query, this.rootProjectionNode); 
                    this.queryResults = this.query;
                } 
            }
            else
            {
                // Special case where although there were expand expressions, they were ignored because they did not refer to entity sets 
                if (!String.IsNullOrEmpty(this.service.OperationContext.Host.GetQueryStringItem(XmlConstants.HttpQueryStringExpand)))
                { 
                    this.ProjectSkipTokenForNonExpand(); 
                    this.ProcessOrderBy();
                    this.ProcessSkipAndTop(); 
                }

                this.queryResults = this.query;
            } 

            Debug.Assert(this.queryResults != null, "this.queryResults != null"); 
        } 

        /// Processes the $filter argument of the request. 
        private void ProcessFilter()
        {
            string filter = this.service.OperationContext.Host.GetQueryStringItem(XmlConstants.HttpQueryStringFilter);
            if (!String.IsNullOrEmpty(filter)) 
            {
                this.CheckFilterQueryApplicable(); 
                this.query = RequestQueryParser.Where(this.service, this.description.LastSegmentInfo.TargetContainer, this.description.TargetResourceType, this.query, filter); 
            }
        } 

        /// Processes the $skiptoken argument of the request.
        private void ProcessSkipToken()
        { 
            // Obtain skip token from query parameters.
            String skipToken = this.service.OperationContext.Host.GetQueryStringItem(XmlConstants.HttpQueryStringSkipToken); 
 
            if (this.pagingApplicable)
            { 
                if (this.IsCustomPaged)
                {
                    this.ApplyCustomPaging(skipToken);
                } 
                else
                { 
                    this.ApplyStandardPaging(skipToken); 
                }
            } 
            else
            if (!String.IsNullOrEmpty(skipToken))
            {
                throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_SkipTokenNotAllowed); 
            }
        } 
 
        /// Applies standard paging to the query.
        /// Skip token obtained from query parameters. 
        private void ApplyStandardPaging(string skipToken)
        {
            Debug.Assert(!this.IsCustomPaged, "Custom paging should be disabled for this function to be called.");
            if (!String.IsNullOrEmpty(skipToken)) 
            {
                // Parse the skipToken to obtain each positional value 
                KeyInstance k = null; 
                WebUtil.CheckSyntaxValid(KeyInstance.TryParseNullableTokens(skipToken, out k));
 
                // Ordering constraint in the presence of skipToken will have the following settings:
                // * First there will be the provided constraints based on $orderby
                // * Followed by all the key columns in the resource type
 
                // Validate that the skipToken had as many positional values as the number of ordering constraints
                if (this.topLevelOrderingInfo.OrderingExpressions.Count != k.PositionalValues.Count) 
                { 
                    throw DataServiceException.CreateBadRequestError(Strings.DataService_SDP_SkipTokenNotMatchingOrdering(k.PositionalValues.Count, skipToken, this.topLevelOrderingInfo.OrderingExpressions.Count));
                } 

                // Build the Where clause with the provided skip token
                this.query = RequestUriProcessor.InvokeWhereForType(
                                this.query, 
                                this.orderingParser.BuildSkipTokenFilter(this.topLevelOrderingInfo, k));
 
                // $skiptoken is expected to be only sent by 2.0 & above clients 
                this.description.RaiseMinimumVersionRequirement(2, 0);
                this.description.RaiseResponseVersion(2, 0); 
            }
        }

        /// Applies custom paging to the query. 
        /// Skip token obtained from query parameters.
        private void ApplyCustomPaging(string skipToken) 
        { 
            Debug.Assert(this.IsCustomPaged, "Custom paging should be enabled for this function to be called.");
            if (!String.IsNullOrEmpty(skipToken)) 
            {
                // Parse the skipToken to obtain each positional value
                KeyInstance k = null;
                WebUtil.CheckSyntaxValid(KeyInstance.TryParseNullableTokens(skipToken, out k)); 

                ParameterExpression p = Expression.Parameter(this.description.LastSegmentInfo.TargetResourceType.InstanceType, "it"); 
 
                RequestQueryParser.ExpressionParser skipTokenParser = new RequestQueryParser.ExpressionParser(
                                            this.service, 
                                            this.description.LastSegmentInfo.TargetContainer,
                                            this.description.LastSegmentInfo.TargetResourceType,
                                            p,
                                            ""); 

                object[] convertedValues = new object[k.PositionalValues.Count]; 
                int i = 0; 
                foreach (var value in k.PositionalValues)
                { 
                    convertedValues[i++] = skipTokenParser.ParseSkipTokenLiteral((string)value);
                }

                this.CheckAndApplyCustomPaging(convertedValues); 

                // $skiptoken is expected to be only sent by 2.0 & above clients 
                this.description.RaiseMinimumVersionRequirement(2, 0); 
            }
            else 
            {
                this.CheckAndApplyCustomPaging(null);
            }
        } 

        /// Processes the $orderby argument of the request. 
        private void ProcessOrderBy() 
        {
            Debug.Assert(this.topLevelOrderingInfo != null, "Must have valid ordering information in ProcessOrderBy"); 
            if (this.topLevelOrderingInfo.OrderingExpressions.Count > 0)
            {
                this.query = RequestQueryParser.OrderBy(this.service, this.query, this.topLevelOrderingInfo);
                this.orderApplied = true; 
            }
        } 
 
        /// Processes the $inlinecount argument of the request.
        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining | System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)] 
        private void ProcessCount()
        {
            string count = this.service.OperationContext.Host.GetQueryStringItem(XmlConstants.HttpQueryStringInlineCount);
 
            if (!String.IsNullOrEmpty(count))
            { 
                // Throw if $inlinecount requests have been disabled by the user 
                if (!this.service.Configuration.DataServiceBehavior.AcceptCountRequests)
                { 
                    throw DataServiceException.CreateBadRequestError(Strings.DataServiceConfiguration_CountNotSupportedInV1Server);
                }

                count = count.TrimStart(); 

                // none 
                if (count.Equals(XmlConstants.UriRowCountOffOption)) 
                {
                    return; 
                }

                // only get
                if (this.service.OperationContext.Host.AstoriaHttpVerb != AstoriaVerbs.GET) 
                {
                    throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_RequestVerbCannotCountError); 
                } 

                // if already counting value only, then fail 
                if (this.description.CountOption == RequestQueryCountOption.ValueOnly)
                {
                    throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_InlineCountWithValueCount);
                } 

                // Only applied to a set 
                this.CheckSetQueryApplicable(); 

                MethodInfo mi = countQueryResultMethodInfo.MakeGenericMethod(this.query.ElementType); 

                if (count == XmlConstants.UriRowCountAllOption)
                {
                    this.description.CountValue = (long)mi.Invoke(null, new object[] { this.query }); 
                    this.description.CountOption = RequestQueryCountOption.Inline;
                    this.description.RaiseMinimumVersionRequirement(2, 0); 
                    this.description.RaiseResponseVersion(2, 0); 
                }
                else 
                {
                    throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_InvalidCountOptionError);
                }
            } 
        }
 
        ///  
        /// Builds the collection of ordering expressions including implicit ordering if paging is required at top level
        ///  
        private void ObtainOrderingExpressions()
        {
            const String Comma = ",";
            const char Space = ' '; 
            const String AscendingOrderIdentifier = "asc";
 
            StringBuilder orderBy = new StringBuilder(this.service.OperationContext.Host.GetQueryStringItem(XmlConstants.HttpQueryStringOrderBy)); 
            if (orderBy.Length > 0)
            { 
                this.CheckSetQueryApplicable();
            }

            Debug.Assert(this.topLevelOrderingInfo == null, "Must only be called once per query"); 

            ResourceType rt = this.description.TargetResourceType; 
            Debug.Assert(rt != null || this.setQueryApplicable == false, "Resource type must be known for Ordering to be applied."); 

            this.topLevelOrderingInfo = new OrderingInfo(this.IsStandardPaged); 

            // We need to generate ordering expression, if either the result is paged, or we have
            // skip or top count request because in that case, the skip or top has to happen in
            // the expand provider 
            if (this.IsStandardPaged || this.topCount.HasValue || this.skipCount.HasValue)
            { 
                // Additional ordering expressions in case paging is supported 
                String separator = orderBy.Length > 0 ? Comma : String.Empty;
                foreach (var keyProp in rt.KeyProperties) 
                {
                    orderBy.Append(separator).Append(keyProp.Name).Append(Space).Append(AscendingOrderIdentifier);
                    separator = Comma;
                } 

                Debug.Assert(this.query.ElementType == rt.InstanceType, "Resource type should match query element type when in this function"); 
            } 

            String orderByText = orderBy.ToString(); 

            if (!String.IsNullOrEmpty(orderByText))
            {
                ParameterExpression elementParameter = Expression.Parameter(rt.InstanceType, "element"); 

                this.orderingParser = new RequestQueryParser.ExpressionParser( 
                                            this.service, 
                                            this.description.LastSegmentInfo.TargetContainer,
                                            rt, 
                                            elementParameter,
                                            orderByText);

                IEnumerable ordering = this.orderingParser.ParseOrdering(); 

                foreach (OrderingExpression o in ordering) 
                { 
                    this.topLevelOrderingInfo.Add(new OrderingExpression(Expression.Lambda(o.Expression, elementParameter), o.IsAscending));
                } 

                if (this.IsStandardPaged)
                {
                    this.description.SkipTokenExpressionCount = this.topLevelOrderingInfo.OrderingExpressions.Count; 
                    this.description.SkipTokenProperties = NeedSkipTokenVisitor.CollectSkipTokenProperties(this.topLevelOrderingInfo, rt);
                } 
            } 
        }
 
        /// 
        /// Processes query arguments and returns a request description for
        /// the resulting query.
        ///  
        /// A modified  that includes query information.
        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining | System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)] 
        private RequestDescription ProcessQuery() 
        {
            // Obtains the values of $skip and $top arguments 
            this.ObtainSkipTopCounts();

            // Obtain ordering information for the current request
            this.ObtainOrderingExpressions(); 

            // NOTE: The order set by ProcessOrderBy may be reset by other operations other than skip/top, 
            // so filtering needs to occur first. 
            this.ProcessFilter();
 
            // $inlinecount ignores SDP, Skip and Top
            this.ProcessCount();

            // $skiptoken is just like a filter, so it should be immediately after $filter 
            this.ProcessSkipToken();
 
            if (String.IsNullOrEmpty(this.service.OperationContext.Host.GetQueryStringItem(XmlConstants.HttpQueryStringExpand)) 
                && String.IsNullOrEmpty(this.service.OperationContext.Host.GetQueryStringItem(XmlConstants.HttpQueryStringSelect)))
            { 
                this.ProjectSkipTokenForNonExpand();
                this.ProcessOrderBy();
                this.ProcessSkipAndTop();
            } 
            else if (this.description.CountOption == RequestQueryCountOption.ValueOnly)
            { 
                // We need to take $top and $skip into account when executing $count requests. 
                // The issue is if there is a projection, we push the $skip and $top into the projection node
                // and hence we miss it when $count with $select is specified. 
                this.ProcessOrderBy();
                this.ProcessSkipAndTop();
            }
 
            // NOTE: expand goes last, as it may be reset by other operations.
            this.ProcessExpand(); 
 
            this.ProcessSelect();
 
            this.GenerateQueryResult();
#if DEBUG
            // We analyze the query here to detect if we have incorrectly inserted
            // calls to OTM for non-open types 
            if (this.service.Provider.AreAllResourceTypesNonOpen)
            { 
                // Verify that there is no call to OTMs here. 
                if (!(this.queryResults is BaseServiceProvider.QueryableOverEnumerable))
                { 
                    OpenTypeMethodCallDetector.CheckForOpenTypeMethodCalls((this.queryResults as IQueryable ?? this.query).Expression);
                }
            }
 
            // This is a hook for exposing the query out to debuggers and tests.
            Type serviceType = this.service.Instance.GetType(); 
            while (serviceType.BaseType != null) 
            {
                try 
                {
                    FieldInfo delegateField = serviceType.GetField("requestQueryableConstructed", BindingFlags.NonPublic | BindingFlags.Instance);
                    if (delegateField != null)
                    { 
                        // Batch services do not have this delegate
                        Action action = delegateField.GetValue(this.service.Instance) as Action; 
                        if (action != null) 
                        {
                            // If the actual results are IQueryable, then report that (since it's the actual query we're going to execute) 
                            if (this.queryResults is IQueryable)
                            {
                                action.Invoke((IQueryable)this.queryResults);
                            } 
                            else
                            { 
                                action.Invoke(this.query); 
                            }
                        } 
                        break;
                    }

                    serviceType = serviceType.BaseType; 
                }
                catch (FieldAccessException) 
                { 
                    // in medium trust settings, we can't get reflection permission
                    // just break out of the loop then 
                    break;
                }
            }
#endif 

            Debug.Assert(this.queryResults != null, "this.queryResults != null -- otherwise ProcessExpand didn't set it"); 
 
            return new RequestDescription(this.description, this.queryResults, this.rootProjectionNode);
        } 

        /// 
        /// In case $expand is not provided while the results are still paged, we need to create a wrapper
        /// for the object in order to project the skip tokens corresponding to the result sequence 
        /// 
        private void ProjectSkipTokenForNonExpand() 
        { 
            if (this.IsStandardPaged && this.description.SkipTokenProperties == null)
            { 
                Type queryElementType = this.query.ElementType;

                ParameterExpression expandParameter = Expression.Parameter(queryElementType, "p");
 
                StringBuilder skipTokenDescription = new StringBuilder();
 
                Type skipTokenWrapperType = this.GetSkipTokenWrapperTypeAndDescription(skipTokenDescription); 

                MemberBinding[] skipTokenBindings = this.GetSkipTokenBindings(skipTokenWrapperType, skipTokenDescription.ToString(), expandParameter); 

                Type resultWrapperType = WebUtil.GetWrapperType(new Type[] { queryElementType, skipTokenWrapperType }, null);

                MemberBinding[] resultWrapperBindings = new MemberBinding[3]; 
                resultWrapperBindings[0] = Expression.Bind(resultWrapperType.GetProperty("ExpandedElement"), expandParameter);
                resultWrapperBindings[1] = Expression.Bind(resultWrapperType.GetProperty("Description"), Expression.Constant(XmlConstants.HttpQueryStringSkipToken)); 
                resultWrapperBindings[2] = Expression.Bind( 
                                                resultWrapperType.GetProperty("ProjectedProperty0"),
                                                Expression.MemberInit(Expression.New(skipTokenWrapperType), skipTokenBindings)); 

                Expression resultBody = Expression.MemberInit(Expression.New(resultWrapperType), resultWrapperBindings);

                this.query = RequestUriProcessor.InvokeSelectForTypes(this.query, resultWrapperType, Expression.Lambda(resultBody, expandParameter)); 

                // Updates the ordering expressions with the ones that take the result wrapper type as parameter 
                // and dereference the ExpandedElement property 
                this.UpdateOrderingInfoWithSkipTokenWrapper(resultWrapperType);
            } 
        }

        /// 
        /// Obtains the wrapper type for the $skiptoken along with description of properties in the wrapper 
        /// 
        /// Description for the skip token properties 
        /// Type of $skiptoken wrapper 
        private Type GetSkipTokenWrapperTypeAndDescription(StringBuilder skipTokenDescription)
        { 
            const String Comma = ",";

            Type[] skipTokenTypes = new Type[this.topLevelOrderingInfo.OrderingExpressions.Count + 1];
 
            skipTokenTypes[0] = this.query.ElementType;
 
            int i = 0; 
            String separator = String.Empty;
            foreach (var ordering in this.topLevelOrderingInfo.OrderingExpressions) 
            {
                skipTokenTypes[i + 1] = ((LambdaExpression)ordering.Expression).Body.Type;
                skipTokenDescription.Append(separator).Append(XmlConstants.SkipTokenPropertyPrefix + i.ToString(System.Globalization.CultureInfo.InvariantCulture));
                separator = Comma; 
                i++;
            } 
 
            return WebUtil.GetWrapperType(skipTokenTypes, Strings.BasicExpandProvider_SDP_UnsupportedOrderingExpressionBreadth);
        } 

        /// 
        /// Given the wrapper type and description, returns bindings for the wrapper type for skip token
        ///  
        /// Wrapper type
        /// Description 
        /// Top level parameter type 
        /// Array of bindings for skip token
        private MemberBinding[] GetSkipTokenBindings(Type skipTokenWrapperType, String skipTokenDescription, ParameterExpression expandParameter) 
        {
            MemberBinding[] skipTokenBindings = new MemberBinding[this.topLevelOrderingInfo.OrderingExpressions.Count + 2];
            skipTokenBindings[0] = Expression.Bind(skipTokenWrapperType.GetProperty("ExpandedElement"), expandParameter);
            skipTokenBindings[1] = Expression.Bind(skipTokenWrapperType.GetProperty("Description"), Expression.Constant(skipTokenDescription.ToString())); 

            int i = 0; 
            foreach (var ordering in this.topLevelOrderingInfo.OrderingExpressions) 
            {
                LambdaExpression sourceLambda = (LambdaExpression)ordering.Expression; 
                Expression source = ParameterReplacerVisitor.Replace(sourceLambda.Body, sourceLambda.Parameters[0], expandParameter);
                MemberInfo member = skipTokenWrapperType.GetProperty("ProjectedProperty" + i.ToString(System.Globalization.CultureInfo.InvariantCulture));
                skipTokenBindings[i + 2] = Expression.Bind(member, source);
                i++; 
            }
 
            return skipTokenBindings; 
        }
 
        /// 
        /// Updates the topLevelOrderingInfo member with the new collection of expressions that
        /// dereference the ExpandedElement property on the top level wrapper object
        ///  
        /// Type of top level wrapper object
        private void UpdateOrderingInfoWithSkipTokenWrapper(Type resultWrapperType) 
        { 
            OrderingInfo newOrderingInfo = new OrderingInfo(true);
 
            ParameterExpression wrapperParameter = Expression.Parameter(resultWrapperType, "w");

            foreach (var ordering in this.topLevelOrderingInfo.OrderingExpressions)
            { 
                LambdaExpression oldExpression = (LambdaExpression)ordering.Expression;
                Expression newOrdering = ParameterReplacerVisitor.Replace( 
                                            oldExpression.Body, 
                                            oldExpression.Parameters[0],
                                            Expression.MakeMemberAccess(wrapperParameter, resultWrapperType.GetProperty("ExpandedElement"))); 

                newOrderingInfo.Add(new OrderingExpression(Expression.Lambda(newOrdering, wrapperParameter), ordering.IsAscending));
            }
 
            this.topLevelOrderingInfo = newOrderingInfo;
        } 
 
        /// Processes the $skip and/or $top argument of the request by composing query with Skip and/or Take methods.
        [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining | System.Runtime.CompilerServices.MethodImplOptions.NoOptimization)] 
        private void ProcessSkipAndTop()
        {
            Debug.Assert(this.query != null, "this.query != null");
            if (this.skipCount.HasValue) 
            {
                Debug.Assert(this.orderApplied, "Ordering must have already been applied."); 
                this.query = (IQueryable)RequestQueryProcessor 
                                            .InvokeSkipMethodInfo
                                            .MakeGenericMethod(this.query.ElementType) 
                                            .Invoke(null, new object[] { this.query, this.skipCount.Value });
                Debug.Assert(this.query != null, "this.query != null");
            }
 
            if (this.topCount.HasValue)
            { 
                Debug.Assert(this.orderApplied, "Ordering must have already been applied."); 
                this.query = (IQueryable)RequestQueryProcessor
                                            .InvokeTakeMethodInfo 
                                            .MakeGenericMethod(this.query.ElementType)
                                            .Invoke(null, new object[] { this.query, this.topCount.Value });
                Debug.Assert(this.query != null, "this.query != null");
            } 
        }
 
        ///  
        /// Finds out the appropriate value for skip and top parameters for the current request
        ///  
        private void ObtainSkipTopCounts()
        {
            int count;
 
            if (this.ReadSkipOrTopArgument(XmlConstants.HttpQueryStringSkip, out count))
            { 
                this.skipCount = count; 
            }
 
            int pageSize = 0;
            if (this.IsStandardPaged)
            {
                pageSize = this.description.LastSegmentInfo.TargetContainer.PageSize; 
            }
 
            if (this.ReadSkipOrTopArgument(XmlConstants.HttpQueryStringTop, out count)) 
            {
                this.topCount = count; 
                if (this.IsStandardPaged && pageSize < this.topCount.Value)
                {
                    // If $top is greater than or equal to page size, we will need a $skiptoken and
                    // thus our response will be 2.0 
                    this.description.RaiseResponseVersion(2, 0);
                    this.topCount = pageSize; 
                } 
            }
            else if (this.IsStandardPaged) 
            {
                // Paging forces response version of 2.0
                this.description.RaiseResponseVersion(2, 0);
                this.topCount = pageSize; 
            }
 
            if (this.topCount != null || this.skipCount != null) 
            {
                this.CheckSetQueryApplicable(); 
            }
        }

        ///  
        /// Checks whether the specified argument should be processed and what
        /// its value is. 
        ///  
        /// Name of the query item, $top or $skip.
        /// The value for the query item. 
        /// true if the argument should be processed; false otherwise.
        private bool ReadSkipOrTopArgument(string queryItem, out int count)
        {
            Debug.Assert(queryItem != null, "queryItem != null"); 

            string itemText = this.service.OperationContext.Host.GetQueryStringItem(queryItem); 
            if (String.IsNullOrEmpty(itemText)) 
            {
                count = 0; 
                return false;
            }

            if (!Int32.TryParse(itemText, NumberStyles.Integer, CultureInfo.InvariantCulture, out count)) 
            {
                throw DataServiceException.CreateSyntaxError( 
                    Strings.RequestQueryProcessor_IncorrectArgumentFormat(queryItem, itemText)); 
            }
 
            return true;
        }

        /// Checks if custom paging is already applied, if not, applies it and raises response version. 
        /// Values of skip tokens.
        private void CheckAndApplyCustomPaging(object[] skipTokenValues) 
        { 
            if (!this.appliedCustomPaging)
            { 
                this.service.PagingProvider.PagingProviderInterface.SetContinuationToken(
                        this.query,
                        this.description.LastSegmentInfo.TargetResourceType,
                        skipTokenValues); 

                this.description.RaiseResponseVersion(2, 0); 
                this.appliedCustomPaging = true; 
            }
        } 

#if DEBUG
        /// Detects calls to OpenTypeMethods members and asserts if it finds any.
        private class OpenTypeMethodCallDetector : ALinqExpressionVisitor 
        {
            /// Public interface for using this class. 
            /// Input expression. 
            public static void CheckForOpenTypeMethodCalls(Expression input)
            { 
                new OpenTypeMethodCallDetector().Visit(input);
            }

            /// Forgiving Visit method which allows unknown expression types to pass through. 
            /// Input expression.
            /// Visit expression. 
            internal override Expression Visit(Expression exp) 
            {
                try 
                {
                    return base.Visit(exp);
                }
                catch (NotSupportedException) 
                {
                    return exp; 
                } 
            }
 
            /// Method call visitor.
            /// Method call being visited.
            /// Whatever is provided on input.
            internal override Expression VisitMethodCall(MethodCallExpression m) 
            {
                if (m.Method.DeclaringType == typeof(OpenTypeMethods)) 
                { 
                    throw new InvalidOperationException("Unexpected call to OpenTypeMethods found in query when the provider did not have any OpenTypes.");
                } 

                return base.VisitMethodCall(m);
            }
 
            internal override Expression VisitBinary(BinaryExpression b)
            { 
                if (b.Method != null && b.Method.DeclaringType == typeof(OpenTypeMethods)) 
                {
                    throw new InvalidOperationException("Unexpected call to OpenTypeMethods found in query when the provider did not have any OpenTypes."); 
                }

                return base.VisitBinary(b);
            } 

            internal override Expression VisitUnary(UnaryExpression u) 
            { 
                if (u.Method != null && u.Method.DeclaringType == typeof(OpenTypeMethods))
                { 
                    throw new InvalidOperationException("Unexpected call to OpenTypeMethods found in query when the provider did not have any OpenTypes.");
                }

                return base.VisitUnary(u); 
            }
        } 
#endif 
    }
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.

                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK