Пример #1
0
        /// <summary>
        /// Core logic for applying the query option to the IQueryable.
        /// </summary>
        /// <param name="query">The original <see cref="IQueryable"/>.</param>
        /// <param name="querySettings">Query setting used for validating the query option.</param>
        /// <param name="orderByNodes">OrderBy information required to correctly apply the query option for default implementation.</param>
        /// <param name="context">The <see cref="ODataQueryContext"/> which contains the <see cref="IEdmModel"/> and some type information</param>
        /// <param name="skipTokenRawValue">The raw string value of the skiptoken query parameter.</param>
        /// <returns></returns>
        private static IQueryable ApplyToCore(IQueryable query, ODataQuerySettings querySettings, IList <OrderByNode> orderByNodes, ODataQueryContext context, string skipTokenRawValue)
        {
            if (query == null)
            {
                throw Error.ArgumentNull(nameof(query));
            }

            if (context.ElementClrType == null)
            {
                throw Error.NotSupported(SRResources.ApplyToOnUntypedQueryOption, "ApplyTo");
            }

            IDictionary <string, OrderByDirection> directionMap;

            if (orderByNodes != null)
            {
                directionMap =
                    orderByNodes.OfType <OrderByPropertyNode>().ToDictionary(node => node.Property.Name, node => node.Direction);
            }
            else
            {
                directionMap = new Dictionary <string, OrderByDirection>();
            }

            IDictionary <string, object> propertyValuePairs = PopulatePropertyValuePairs(skipTokenRawValue, context);

            if (propertyValuePairs.Count == 0)
            {
                throw Error.InvalidOperation("Unable to get property values from the skiptoken value.");
            }

            ExpressionBinderBase binder = new FilterBinder(context.RequestContainer);
            bool parameterizeConstant   = querySettings.EnableConstantParameterization;
            ParameterExpression param   = Expression.Parameter(context.ElementClrType);

            Expression where = null;

            /* We will create a where lambda of the following form -
             * Where (Prop1>Value1)
             * OR (Prop1=Value1 AND Prop2>Value2)
             * OR (Prop1=Value1 AND Prop2=Value2 AND Prop3>Value3)
             * and so on...
             * Adding the first true to simplify implementation.
             */
            Expression lastEquality  = null;
            bool       firstProperty = true;

            foreach (KeyValuePair <string, object> item in propertyValuePairs)
            {
                string           key      = item.Key;
                MemberExpression property = Expression.Property(param, key);
                object           value    = item.Value;

                Expression     compare   = null;
                ODataEnumValue enumValue = value as ODataEnumValue;
                if (enumValue != null)
                {
                    value = enumValue.Value;
                }

                Expression constant = parameterizeConstant ? LinqParameterContainer.Parameterize(value.GetType(), value) : Expression.Constant(value);
                if (directionMap.ContainsKey(key) && directionMap[key] == OrderByDirection.Descending)
                {
                    compare = binder.CreateBinaryExpression(BinaryOperatorKind.LessThan, property, constant, true);
                }
                else
                {
                    compare = binder.CreateBinaryExpression(BinaryOperatorKind.GreaterThan, property, constant, true);
                }

                if (firstProperty)
                {
                    lastEquality  = binder.CreateBinaryExpression(BinaryOperatorKind.Equal, property, constant, true);
                    where         = compare;
                    firstProperty = false;
                }
                else
                {
                    Expression condition = Expression.AndAlso(lastEquality, compare);
                    where        = Expression.OrElse(where, condition);
                    lastEquality = Expression.AndAlso(lastEquality, binder.CreateBinaryExpression(BinaryOperatorKind.Equal, property, constant, true));
                }
            }

            Expression whereLambda = Expression.Lambda(where, param);

            return(ExpressionHelpers.Where(query, whereLambda, query.ElementType));
        }
Пример #2
0
        private IOrderedQueryable ApplyToCore(IQueryable query, ODataQuerySettings querySettings)
        {
            if (Context.ElementClrType == null)
            {
                throw Error.NotSupported(SRResources.ApplyToOnUntypedQueryOption, "ApplyTo");
            }

            ICollection <OrderByNode> nodes = OrderByNodes;

            bool       alreadyOrdered = false;
            IQueryable querySoFar     = query;

            HashSet <IEdmProperty> propertiesSoFar     = new HashSet <IEdmProperty>();
            HashSet <string>       openPropertiesSoFar = new HashSet <string>();
            bool orderByItSeen = false;

            foreach (OrderByNode node in nodes)
            {
                OrderByPropertyNode     propertyNode     = node as OrderByPropertyNode;
                OrderByOpenPropertyNode openPropertyNode = node as OrderByOpenPropertyNode;

                if (propertyNode != null)
                {
                    IEdmProperty     property  = propertyNode.Property;
                    OrderByDirection direction = propertyNode.Direction;

                    // This check prevents queries with duplicate properties (e.g. $orderby=Id,Id,Id,Id...) from causing stack overflows
                    if (propertiesSoFar.Contains(property))
                    {
                        throw new ODataException(Error.Format(SRResources.OrderByDuplicateProperty, property.Name));
                    }
                    propertiesSoFar.Add(property);

                    if (propertyNode.OrderByClause != null)
                    {
                        querySoFar = AddOrderByQueryForProperty(query, querySettings, propertyNode.OrderByClause, querySoFar, direction, alreadyOrdered);
                    }
                    else
                    {
                        querySoFar = ExpressionHelpers.OrderByProperty(querySoFar, Context.Model, property, direction, Context.ElementClrType, alreadyOrdered);
                    }
                    alreadyOrdered = true;
                }
                else if (openPropertyNode != null)
                {
                    // This check prevents queries with duplicate properties (e.g. $orderby=Id,Id,Id,Id...) from causing stack overflows
                    if (openPropertiesSoFar.Contains(openPropertyNode.PropertyName))
                    {
                        throw new ODataException(Error.Format(SRResources.OrderByDuplicateProperty, openPropertyNode.PropertyName));
                    }
                    openPropertiesSoFar.Add(openPropertyNode.PropertyName);
                    Contract.Assert(openPropertyNode.OrderByClause != null);
                    querySoFar     = AddOrderByQueryForProperty(query, querySettings, openPropertyNode.OrderByClause, querySoFar, openPropertyNode.Direction, alreadyOrdered);
                    alreadyOrdered = true;
                }
                else
                {
                    // This check prevents queries with duplicate nodes (e.g. $orderby=$it,$it,$it,$it...) from causing stack overflows
                    if (orderByItSeen)
                    {
                        throw new ODataException(Error.Format(SRResources.OrderByDuplicateIt));
                    }

                    querySoFar     = ExpressionHelpers.OrderByIt(querySoFar, node.Direction, Context.ElementClrType, alreadyOrdered);
                    alreadyOrdered = true;
                    orderByItSeen  = true;
                }
            }

            return(querySoFar as IOrderedQueryable);
        }
        /// <summary>
        /// Apply the individual query to the given IQueryable in the right order.
        /// </summary>
        /// <param name="query">The original <see cref="IQueryable"/>.</param>
        /// <param name="querySettings">The settings to use in query composition.</param>
        /// <param name="pageSize">The page size for this query</param>
        /// <returns>The new <see cref="IQueryable"/> after the query has been applied to.</returns>
        public virtual IQueryable ApplyTo(IQueryable query, ODataQuerySettings querySettings, int?pageSize)
        {
            if (query == null)
            {
                throw Error.ArgumentNull("query");
            }

            // Construct the actual query and apply them in the following order: filter
            if (Filter != null)
            {
                query = Filter.ApplyTo(query, querySettings, _assemblyProvider);
            }

            var orderBy = OrderBy;

            // $skip or $top require a stable sort for predictable results.
            // Result limits require a stable sort to be able to generate a next page link.
            // If either is present in the query and we have permission,
            // generate an $orderby that will produce a stable sort.
            if (querySettings.EnsureStableOrdering &&
                (Skip != null ||
                 Top != null ||
                 pageSize.HasValue))
            {
                // If there is no OrderBy present, we manufacture a default.
                // If an OrderBy is already present, we add any missing
                // properties necessary to make a stable sort.
                // Instead of failing early here if we cannot generate the OrderBy,
                // let the IQueryable backend fail (if it has to).
                orderBy = orderBy == null
                            ? OrderByHelper.GenerateDefaultOrderBy(Context, _serviceProvider)
                            : OrderByHelper.EnsureStableSortOrderBy(orderBy, Context, _serviceProvider);
            }

            // First apply $apply
            // Section 3.15 of the spec http://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/cs01/odata-data-aggregation-ext-v4.0-cs01.html#_Toc378326311
            if (Apply != null)
            {
                query = Apply.ApplyTo(query, querySettings, _assemblyProvider);
                Request.ODataFeature().ApplyClause = Apply.ApplyClause;
                Context.ElementClrType = Apply.ResultClrType;
            }

            if (orderBy != null)
            {
                query = orderBy.ApplyTo(query, querySettings);
            }

            if (Skip.HasValue)
            {
                query = ExpressionHelpers.Skip(query, Skip.Value, Context.ElementClrType, false);
            }

            int?take = null;

            if (querySettings.PageSize.HasValue)
            {
                take = Math.Min(querySettings.PageSize.Value, int.MaxValue);
            }
            if (Top.HasValue)
            {
                take = Math.Min(Top.Value, take ?? int.MaxValue);
            }
            if (take.HasValue)
            {
                query = ExpressionHelpers.Take(query, take.Value, Context.ElementClrType, false);
            }

            if (SelectExpand != null)
            {
                query = SelectExpand.ApplyTo(query, querySettings, _assemblyProvider);
            }

            if (CountQueryOption != null)
            {
                if (Request.ODataFeature().TotalCountFunc == null)
                {
                    Func <long> countFunc = CountQueryOption.GetEntityCountFunc(query);
                    if (countFunc != null)
                    {
                        Request.ODataFeature().TotalCountFunc = countFunc;
                    }
                }

                if (ODataCountMediaTypeMapping.IsCountRequest(Request.HttpContext))
                {
                    return(query);
                }
            }
            if (pageSize.HasValue)
            {
                bool resultsLimited;
                query = LimitResults(query, pageSize.Value, out resultsLimited);
                if (resultsLimited && Request.GetDisplayUrl() != null && new Uri(Request.GetDisplayUrl()).IsAbsoluteUri&& Request.ODataFeature().NextLink == null)
                {
                    Uri nextPageLink = Request.GetNextPageLink(pageSize.Value);
                    Request.ODataFeature().NextLink = nextPageLink;
                }
            }
            return(query);
        }