/// <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)); }
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); }