RequestQueryProcessor.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / Orcas / NetFXw7 / ndp / fx / src / DataWeb / Server / System / Data / Services / RequestQueryProcessor.cs / 1 / 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.Parsing;
    using System.Data.Services.Providers; 
    using System.Diagnostics;
    using System.Globalization;
    using System.Linq;
    using System.Linq.Expressions; 
    using System.Reflection;
 
    /// Use this class to process a web data service request URI. 
    internal struct RequestQueryProcessor
    { 
        #region Private fields.

        /// 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 options can be applied to the request.
        private readonly bool setQueryApplicable; 

        /// List of paths to be expanded. 
        private List expandPaths; 

        /// List of paths to be expanded, before resolving the identifiers 
        private List> expandPathsAsText;

        /// Whether any order has been applied.
        private bool orderApplied; 

        /// 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.query = (IQueryable)description.RequestEnumerable; 
            this.filterQueryApplicable =
                description.TargetKind == RequestTargetKind.Resource || 
#if ASTORIA_OPEN_OBJECT 
                description.TargetKind == RequestTargetKind.OpenProperty ||
#endif 
                description.TargetKind == RequestTargetKind.ComplexObject;
            this.setQueryApplicable = (
#if ASTORIA_OPEN_OBJECT
                description.TargetKind == RequestTargetKind.OpenProperty || 
#endif
 
                 description.TargetKind == RequestTargetKind.Resource) && !description.IsSingleResult; 
            this.expandPaths = null;
            this.expandPathsAsText = null; 
            this.queryResults = null;
        }

        /// 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 CheckEmptyQueryArguments(IDataService service)
        { 
            Debug.Assert(service != null, "service != null");
 
            IDataServiceHost host = service.Host; 
            if (!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)))
            { 
                // 400: Bad Request
                throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QueryNoOptionsApplicable); 
            } 
        }
 
        /// 
        /// 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 = System.Data.Services.Parsing.RequestQueryParser.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");

            // When the request doesn't produce an IQueryable result,
            // we can short-circuit all further processing. 
            if (!(description.RequestEnumerable is IQueryable))
            { 
                RequestQueryProcessor.CheckEmptyQueryArguments(service); 
                return description;
            } 
            else
            {
                return new RequestQueryProcessor(service, description).ProcessQuery();
            } 
        }
 
        /// Generic method to invoke an OrderBy or OrderByDescending method on an IQueryable source. 
        /// Element type of the source.
        /// Result type of the key. 
        /// Source query.
        /// Lambda expression that turns TSource into TKey.
        /// Whether the ordering should be ascending or not.
        /// Whether this is the first-level order; false if this is an inner-level order-by. 
        /// A new query that sorts TSource by TKey.
        private static IQueryable InvokeOrderBy(IQueryable source, LambdaExpression keySelector, bool ascending, bool firstLevelOrder) 
        { 
            Debug.Assert(source != null, "source != null");
            Debug.Assert(keySelector != null, "keySelector != null"); 

            Expression> typedKeySelector = (Expression>)keySelector;
            if (firstLevelOrder)
            { 
                IQueryable typedSource = (IQueryable)source;
                if (ascending) 
                { 
                    return Queryable.OrderBy(typedSource, typedKeySelector);
                } 
                else
                {
                    return Queryable.OrderByDescending(typedSource, typedKeySelector);
                } 
            }
            else 
            { 
                IOrderedQueryable typedSource = (IOrderedQueryable)source;
                if (ascending) 
                {
                    return Queryable.ThenBy(typedSource, typedKeySelector);
                }
                else 
                {
                    return Queryable.ThenByDescending(typedSource, typedKeySelector); 
                } 
            }
        } 

        /// 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); 
        }
 
        /// Composes an OrderBy operation on the specified query.
        /// Query to compose over.
        /// Property to order by.
        /// Whether the ordering should be ascending or descending. 
        /// Whether this is the first-level order; false if this is an inner-level order-by.
        /// A query that orders the specified query. 
        private static IQueryable OrderBy(IQueryable query, PropertyInfo property, bool ascending, bool firstLevelOrder) 
        {
            Debug.Assert(query != null, "query != null"); 
            Debug.Assert(property != null, "property != null");

            ParameterExpression parameter = Expression.Parameter(query.ElementType, "element");
            MemberExpression body = Expression.Property(parameter, property); 
            LambdaExpression selector = Expression.Lambda(body, parameter);
 
            MethodInfo method = typeof(RequestQueryProcessor).GetMethod("InvokeOrderBy", BindingFlags.Static | BindingFlags.NonPublic); 
            method = method.MakeGenericMethod(query.ElementType, body.Type);
            return (IQueryable)method.Invoke(null, new object[] { query, selector, ascending, firstLevelOrder }); 
        }

        /// Reads an $expand clause.
        /// Value to read. 
        /// A list of paths, each of which is a list of identifiers.
        private static List> ReadExpand(string expand) 
        { 
            Debug.Assert(!String.IsNullOrEmpty(expand), "!String.IsNullOrEmpty(expand)");
 
            List> result = new List>();
            List currentPath = null;
            ExpressionLexer lexer = new ExpressionLexer(expand);
            while (lexer.CurrentToken.Id != TokenId.End) 
            {
                string 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 (tokenId == TokenId.Comma)
                    {
                        currentPath = null; 
                    }
                    else 
                    { 
                        lexer.ValidateToken(TokenId.Slash);
                    } 

                    lexer.NextToken();
                }
            } 

            return result; 
        } 

        /// Composes a Skip operation on the specified query. 
        /// Query to compose over.
        /// Number of elements to skip.
        /// A query that skips the specified number of elements.
        private static IQueryable Skip(IQueryable query, int count) 
        {
            Debug.Assert(query != null, "query != null"); 
 
            MethodInfo method = typeof(RequestQueryProcessor).GetMethod("InvokeSkip", BindingFlags.Static | BindingFlags.NonPublic);
            method = method.MakeGenericMethod(query.ElementType); 
            return (IQueryable)method.Invoke(null, new object[] { query, count });
        }

        /// Composes a Take operation on the specified query. 
        /// Query to compose over.
        /// Number of elements to take. 
        /// A query that takes up to a specific number of elements. 
        private static IQueryable Take(IQueryable query, int count)
        { 
            Debug.Assert(query != null, "query != null");

            MethodInfo method = typeof(RequestQueryProcessor).GetMethod("InvokeTake", BindingFlags.Static | BindingFlags.NonPublic);
            method = method.MakeGenericMethod(query.ElementType); 
            return (IQueryable)method.Invoke(null, new object[] { query, count });
        } 
 
        /// Applies a default order, if possible.
        private void ApplyDefaultOrder() 
        {
            // Check that default ordering should be applied.
            if (this.description.TargetKind == RequestTargetKind.Resource &&
                (this.description.TargetSource == RequestTargetSource.EntitySet || 
                 this.description.TargetSource == RequestTargetSource.Property))
            { 
                ResourceType resourceType = this.service.Provider.GetResourceType(this.description.TargetElementType); 
                Debug.Assert(resourceType != null, "resourceType != null");
                bool firstLevelOrder = true; 
                foreach (ResourceProperty keyProperty in resourceType.KeyProperties)
                {
                    this.query = OrderBy(this.query, keyProperty.PropertyInfo, true /* ascending */, firstLevelOrder);
                    firstLevelOrder = false; 
                }
            } 
 
            this.orderApplied = true;
        } 

        /// 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--)
            { 
                Type type = this.query.ElementType;
                ResourceType resourceType = this.service.Provider.GetResourceType(type); 
                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];
#if ASTORIA_OPEN_OBJECT 
                    if (resourceType.IsOpenType)
                    { 
                        ignorePath = false; 
                        segments.Add(new ExpandSegment(pathSegment, null));
                        continue; 
                    }
#endif

                    ResourceProperty property = resourceType.TryResolvePropertyName(pathSegment); 
                    if (property == null)
                    { 
                        throw DataServiceException.CreateSyntaxError(Strings.RequestUriProcessor_PropertyNotFound(resourceType.FullName, pathSegment)); 
                    }
 
                    Expression filter = null;
                    if (property.ResourceContainer != null)
                    {
                        bool singleResult = property.Kind == ResourcePropertyKind.ResourceReference; 
                        this.service.Configuration.CheckResourceRightsForRead(property.ResourceContainer, singleResult);
                        filter = DataServiceConfiguration.ComposeQueryInterceptors(this.service, property.ResourceContainer); 
                    } 

                    int expected = this.service.Configuration.MaxResultsPerCollection; 
                    segments.Add(new ExpandSegment(property.Name, filter, expected, property.ResourceContainer));
                    resourceType = property.ResourceType;
                    ignorePath = ignorePath || property.TypeKind != ResourceTypeKind.EntityType;
                } 

                // 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);
                } 
            } 
        }
 
        /// Checks that all resolved expand paths are valid.
        private void CheckExpandUpdatedPaths()
        {
            Debug.Assert(this.expandPaths != null, "this.expandPaths != null"); 
            for (int i = this.expandPaths.Count - 1; i >= 0; i--)
            { 
                Type type = this.query.ElementType; 
                ResourceType resourceType = this.service.Provider.GetResourceType(type);
                ExpandSegmentCollection path = this.expandPaths[i]; 
                for (int j = 0; j < path.Count; j++)
                {
                    string pathSegment = path[j].Name;
#if ASTORIA_OPEN_OBJECT 
                    if (resourceType.IsOpenType)
                    { 
                        break; 
                    }
#endif 

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

                // No need to strip primitive types for object context; the framework will throw, but the user code
                // should know not to add them.
            } 
        }
 
        /// 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.Host.GetQueryStringItem(XmlConstants.HttpQueryStringExpand);
            if (!String.IsNullOrEmpty(expand))
            {
                this.expandPathsAsText = ReadExpand(expand); 
            }
            else 
            { 
                this.expandPathsAsText = new List>();
            } 

            this.CheckExpandPaths();
            this.service.InternalApplyingExpansions(this.query, this.expandPaths);
            this.CheckExpandUpdatedPaths(); 
            if (this.expandPaths.Count > 0)
            { 
                this.queryResults = ((IExpandProvider)this.service.Provider).ApplyExpansions(this.query, this.expandPaths); 
            }
            else 
            {
                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.Host.GetQueryStringItem(XmlConstants.HttpQueryStringFilter);
            if (!String.IsNullOrEmpty(filter))
            { 
                this.CheckFilterQueryApplicable();
                this.query = RequestQueryParser.Where(this.service, this.query, filter); 
            } 
        }
 
        /// Processes the $orderby argument of the request.
        private void ProcessOrderBy()
        {
            string orderBy = this.service.Host.GetQueryStringItem(XmlConstants.HttpQueryStringOrderBy); 
            if (!String.IsNullOrEmpty(orderBy))
            { 
                this.CheckSetQueryApplicable(); 
                IQueryable orderedQuery = RequestQueryParser.OrderBy(this.service, this.query, orderBy);
                if (orderedQuery != this.query) 
                {
                    this.query = orderedQuery;
                    this.orderApplied = true;
                } 
            }
        } 
 
        /// 
        /// Processes query arguments and returns a request description for 
        /// the resulting query.
        /// 
        /// A modified  that includes query information.
        private RequestDescription ProcessQuery() 
        {
            // NOTE: The order set by ProcessOrderBy may be reset by other operations other than skip/top, 
            // so filtering needs to occur first. 
            this.ProcessFilter();
            this.ProcessOrderBy(); 
            this.ProcessSkip();
            this.ProcessTop();

            // NOTE: expand goes last, as it may be reset by other operations. 
            this.ProcessExpand();
            Debug.Assert(this.queryResults != null, "this.queryResults != null -- otherwise ProcessExpand didn't set it"); 
 
            return new RequestDescription(this.description, this.queryResults, this.expandPaths);
        } 

        /// Processes the $skip argument of the request.
        private void ProcessSkip()
        { 
            int count;
            if (this.ProcessSkipOrTopArgument(XmlConstants.HttpQueryStringSkip, out count)) 
            { 
                this.CheckSetQueryApplicable();
                this.query = Skip(this.query, count); 
            }
        }

        ///  
        /// 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 ProcessSkipOrTopArgument(string queryItem, out int count)
        {
            Debug.Assert(queryItem != null, "queryItem != null"); 

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

            if (!this.orderApplied) 
            {
                this.ApplyDefaultOrder(); 
            } 

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

            return true; 
        } 

        /// Processes the $top argument of the request. 
        private void ProcessTop()
        {
            int count;
            if (this.ProcessSkipOrTopArgument(XmlConstants.HttpQueryStringTop, out count)) 
            {
                this.CheckSetQueryApplicable(); 
                this.query = Take(this.query, count); 
            }
        } 
    }
}

// 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.Parsing;
    using System.Data.Services.Providers; 
    using System.Diagnostics;
    using System.Globalization;
    using System.Linq;
    using System.Linq.Expressions; 
    using System.Reflection;
 
    /// Use this class to process a web data service request URI. 
    internal struct RequestQueryProcessor
    { 
        #region Private fields.

        /// 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 options can be applied to the request.
        private readonly bool setQueryApplicable; 

        /// List of paths to be expanded. 
        private List expandPaths; 

        /// List of paths to be expanded, before resolving the identifiers 
        private List> expandPathsAsText;

        /// Whether any order has been applied.
        private bool orderApplied; 

        /// 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.query = (IQueryable)description.RequestEnumerable; 
            this.filterQueryApplicable =
                description.TargetKind == RequestTargetKind.Resource || 
#if ASTORIA_OPEN_OBJECT 
                description.TargetKind == RequestTargetKind.OpenProperty ||
#endif 
                description.TargetKind == RequestTargetKind.ComplexObject;
            this.setQueryApplicable = (
#if ASTORIA_OPEN_OBJECT
                description.TargetKind == RequestTargetKind.OpenProperty || 
#endif
 
                 description.TargetKind == RequestTargetKind.Resource) && !description.IsSingleResult; 
            this.expandPaths = null;
            this.expandPathsAsText = null; 
            this.queryResults = null;
        }

        /// 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 CheckEmptyQueryArguments(IDataService service)
        { 
            Debug.Assert(service != null, "service != null");
 
            IDataServiceHost host = service.Host; 
            if (!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)))
            { 
                // 400: Bad Request
                throw DataServiceException.CreateBadRequestError(Strings.RequestQueryProcessor_QueryNoOptionsApplicable); 
            } 
        }
 
        /// 
        /// 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 = System.Data.Services.Parsing.RequestQueryParser.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");

            // When the request doesn't produce an IQueryable result,
            // we can short-circuit all further processing. 
            if (!(description.RequestEnumerable is IQueryable))
            { 
                RequestQueryProcessor.CheckEmptyQueryArguments(service); 
                return description;
            } 
            else
            {
                return new RequestQueryProcessor(service, description).ProcessQuery();
            } 
        }
 
        /// Generic method to invoke an OrderBy or OrderByDescending method on an IQueryable source. 
        /// Element type of the source.
        /// Result type of the key. 
        /// Source query.
        /// Lambda expression that turns TSource into TKey.
        /// Whether the ordering should be ascending or not.
        /// Whether this is the first-level order; false if this is an inner-level order-by. 
        /// A new query that sorts TSource by TKey.
        private static IQueryable InvokeOrderBy(IQueryable source, LambdaExpression keySelector, bool ascending, bool firstLevelOrder) 
        { 
            Debug.Assert(source != null, "source != null");
            Debug.Assert(keySelector != null, "keySelector != null"); 

            Expression> typedKeySelector = (Expression>)keySelector;
            if (firstLevelOrder)
            { 
                IQueryable typedSource = (IQueryable)source;
                if (ascending) 
                { 
                    return Queryable.OrderBy(typedSource, typedKeySelector);
                } 
                else
                {
                    return Queryable.OrderByDescending(typedSource, typedKeySelector);
                } 
            }
            else 
            { 
                IOrderedQueryable typedSource = (IOrderedQueryable)source;
                if (ascending) 
                {
                    return Queryable.ThenBy(typedSource, typedKeySelector);
                }
                else 
                {
                    return Queryable.ThenByDescending(typedSource, typedKeySelector); 
                } 
            }
        } 

        /// 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); 
        }
 
        /// Composes an OrderBy operation on the specified query.
        /// Query to compose over.
        /// Property to order by.
        /// Whether the ordering should be ascending or descending. 
        /// Whether this is the first-level order; false if this is an inner-level order-by.
        /// A query that orders the specified query. 
        private static IQueryable OrderBy(IQueryable query, PropertyInfo property, bool ascending, bool firstLevelOrder) 
        {
            Debug.Assert(query != null, "query != null"); 
            Debug.Assert(property != null, "property != null");

            ParameterExpression parameter = Expression.Parameter(query.ElementType, "element");
            MemberExpression body = Expression.Property(parameter, property); 
            LambdaExpression selector = Expression.Lambda(body, parameter);
 
            MethodInfo method = typeof(RequestQueryProcessor).GetMethod("InvokeOrderBy", BindingFlags.Static | BindingFlags.NonPublic); 
            method = method.MakeGenericMethod(query.ElementType, body.Type);
            return (IQueryable)method.Invoke(null, new object[] { query, selector, ascending, firstLevelOrder }); 
        }

        /// Reads an $expand clause.
        /// Value to read. 
        /// A list of paths, each of which is a list of identifiers.
        private static List> ReadExpand(string expand) 
        { 
            Debug.Assert(!String.IsNullOrEmpty(expand), "!String.IsNullOrEmpty(expand)");
 
            List> result = new List>();
            List currentPath = null;
            ExpressionLexer lexer = new ExpressionLexer(expand);
            while (lexer.CurrentToken.Id != TokenId.End) 
            {
                string 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 (tokenId == TokenId.Comma)
                    {
                        currentPath = null; 
                    }
                    else 
                    { 
                        lexer.ValidateToken(TokenId.Slash);
                    } 

                    lexer.NextToken();
                }
            } 

            return result; 
        } 

        /// Composes a Skip operation on the specified query. 
        /// Query to compose over.
        /// Number of elements to skip.
        /// A query that skips the specified number of elements.
        private static IQueryable Skip(IQueryable query, int count) 
        {
            Debug.Assert(query != null, "query != null"); 
 
            MethodInfo method = typeof(RequestQueryProcessor).GetMethod("InvokeSkip", BindingFlags.Static | BindingFlags.NonPublic);
            method = method.MakeGenericMethod(query.ElementType); 
            return (IQueryable)method.Invoke(null, new object[] { query, count });
        }

        /// Composes a Take operation on the specified query. 
        /// Query to compose over.
        /// Number of elements to take. 
        /// A query that takes up to a specific number of elements. 
        private static IQueryable Take(IQueryable query, int count)
        { 
            Debug.Assert(query != null, "query != null");

            MethodInfo method = typeof(RequestQueryProcessor).GetMethod("InvokeTake", BindingFlags.Static | BindingFlags.NonPublic);
            method = method.MakeGenericMethod(query.ElementType); 
            return (IQueryable)method.Invoke(null, new object[] { query, count });
        } 
 
        /// Applies a default order, if possible.
        private void ApplyDefaultOrder() 
        {
            // Check that default ordering should be applied.
            if (this.description.TargetKind == RequestTargetKind.Resource &&
                (this.description.TargetSource == RequestTargetSource.EntitySet || 
                 this.description.TargetSource == RequestTargetSource.Property))
            { 
                ResourceType resourceType = this.service.Provider.GetResourceType(this.description.TargetElementType); 
                Debug.Assert(resourceType != null, "resourceType != null");
                bool firstLevelOrder = true; 
                foreach (ResourceProperty keyProperty in resourceType.KeyProperties)
                {
                    this.query = OrderBy(this.query, keyProperty.PropertyInfo, true /* ascending */, firstLevelOrder);
                    firstLevelOrder = false; 
                }
            } 
 
            this.orderApplied = true;
        } 

        /// 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--)
            { 
                Type type = this.query.ElementType;
                ResourceType resourceType = this.service.Provider.GetResourceType(type); 
                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];
#if ASTORIA_OPEN_OBJECT 
                    if (resourceType.IsOpenType)
                    { 
                        ignorePath = false; 
                        segments.Add(new ExpandSegment(pathSegment, null));
                        continue; 
                    }
#endif

                    ResourceProperty property = resourceType.TryResolvePropertyName(pathSegment); 
                    if (property == null)
                    { 
                        throw DataServiceException.CreateSyntaxError(Strings.RequestUriProcessor_PropertyNotFound(resourceType.FullName, pathSegment)); 
                    }
 
                    Expression filter = null;
                    if (property.ResourceContainer != null)
                    {
                        bool singleResult = property.Kind == ResourcePropertyKind.ResourceReference; 
                        this.service.Configuration.CheckResourceRightsForRead(property.ResourceContainer, singleResult);
                        filter = DataServiceConfiguration.ComposeQueryInterceptors(this.service, property.ResourceContainer); 
                    } 

                    int expected = this.service.Configuration.MaxResultsPerCollection; 
                    segments.Add(new ExpandSegment(property.Name, filter, expected, property.ResourceContainer));
                    resourceType = property.ResourceType;
                    ignorePath = ignorePath || property.TypeKind != ResourceTypeKind.EntityType;
                } 

                // 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);
                } 
            } 
        }
 
        /// Checks that all resolved expand paths are valid.
        private void CheckExpandUpdatedPaths()
        {
            Debug.Assert(this.expandPaths != null, "this.expandPaths != null"); 
            for (int i = this.expandPaths.Count - 1; i >= 0; i--)
            { 
                Type type = this.query.ElementType; 
                ResourceType resourceType = this.service.Provider.GetResourceType(type);
                ExpandSegmentCollection path = this.expandPaths[i]; 
                for (int j = 0; j < path.Count; j++)
                {
                    string pathSegment = path[j].Name;
#if ASTORIA_OPEN_OBJECT 
                    if (resourceType.IsOpenType)
                    { 
                        break; 
                    }
#endif 

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

                // No need to strip primitive types for object context; the framework will throw, but the user code
                // should know not to add them.
            } 
        }
 
        /// 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.Host.GetQueryStringItem(XmlConstants.HttpQueryStringExpand);
            if (!String.IsNullOrEmpty(expand))
            {
                this.expandPathsAsText = ReadExpand(expand); 
            }
            else 
            { 
                this.expandPathsAsText = new List>();
            } 

            this.CheckExpandPaths();
            this.service.InternalApplyingExpansions(this.query, this.expandPaths);
            this.CheckExpandUpdatedPaths(); 
            if (this.expandPaths.Count > 0)
            { 
                this.queryResults = ((IExpandProvider)this.service.Provider).ApplyExpansions(this.query, this.expandPaths); 
            }
            else 
            {
                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.Host.GetQueryStringItem(XmlConstants.HttpQueryStringFilter);
            if (!String.IsNullOrEmpty(filter))
            { 
                this.CheckFilterQueryApplicable();
                this.query = RequestQueryParser.Where(this.service, this.query, filter); 
            } 
        }
 
        /// Processes the $orderby argument of the request.
        private void ProcessOrderBy()
        {
            string orderBy = this.service.Host.GetQueryStringItem(XmlConstants.HttpQueryStringOrderBy); 
            if (!String.IsNullOrEmpty(orderBy))
            { 
                this.CheckSetQueryApplicable(); 
                IQueryable orderedQuery = RequestQueryParser.OrderBy(this.service, this.query, orderBy);
                if (orderedQuery != this.query) 
                {
                    this.query = orderedQuery;
                    this.orderApplied = true;
                } 
            }
        } 
 
        /// 
        /// Processes query arguments and returns a request description for 
        /// the resulting query.
        /// 
        /// A modified  that includes query information.
        private RequestDescription ProcessQuery() 
        {
            // NOTE: The order set by ProcessOrderBy may be reset by other operations other than skip/top, 
            // so filtering needs to occur first. 
            this.ProcessFilter();
            this.ProcessOrderBy(); 
            this.ProcessSkip();
            this.ProcessTop();

            // NOTE: expand goes last, as it may be reset by other operations. 
            this.ProcessExpand();
            Debug.Assert(this.queryResults != null, "this.queryResults != null -- otherwise ProcessExpand didn't set it"); 
 
            return new RequestDescription(this.description, this.queryResults, this.expandPaths);
        } 

        /// Processes the $skip argument of the request.
        private void ProcessSkip()
        { 
            int count;
            if (this.ProcessSkipOrTopArgument(XmlConstants.HttpQueryStringSkip, out count)) 
            { 
                this.CheckSetQueryApplicable();
                this.query = Skip(this.query, count); 
            }
        }

        ///  
        /// 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 ProcessSkipOrTopArgument(string queryItem, out int count)
        {
            Debug.Assert(queryItem != null, "queryItem != null"); 

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

            if (!this.orderApplied) 
            {
                this.ApplyDefaultOrder(); 
            } 

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

            return true; 
        } 

        /// Processes the $top argument of the request. 
        private void ProcessTop()
        {
            int count;
            if (this.ProcessSkipOrTopArgument(XmlConstants.HttpQueryStringTop, out count)) 
            {
                this.CheckSetQueryApplicable(); 
                this.query = Take(this.query, count); 
            }
        } 
    }
}

// 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